mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-09 23:49:27 +08:00
Merge branch 'develop' of github.com:nocobase/nocobase into feat-main-datasource-mssql
This commit is contained in:
commit
11578db51c
52
CHANGELOG.md
52
CHANGELOG.md
@ -5,6 +5,58 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [v1.6.16](https://github.com/nocobase/nocobase/compare/v1.6.15...v1.6.16) - 2025-04-03
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- x-disabled property not taking effect on form fields ([#6610](https://github.com/nocobase/nocobase/pull/6610)) by @katherinehhh
|
||||
|
||||
- field label display issue to prevent truncation by colon ([#6599](https://github.com/nocobase/nocobase/pull/6599)) by @katherinehhh
|
||||
|
||||
- **[database]** When deleting one-to-many records, both `filter` and `filterByTk` are passed and `filter` includes an association field, the `filterByTk` is ignored ([#6606](https://github.com/nocobase/nocobase/pull/6606)) by @2013xile
|
||||
|
||||
## [v1.6.15](https://github.com/nocobase/nocobase/compare/v1.6.14...v1.6.15) - 2025-04-01
|
||||
|
||||
### 🚀 Improvements
|
||||
|
||||
- **[database]**
|
||||
- Add trim option for text field ([#6603](https://github.com/nocobase/nocobase/pull/6603)) by @mytharcher
|
||||
|
||||
- Add trim option for string field ([#6565](https://github.com/nocobase/nocobase/pull/6565)) by @mytharcher
|
||||
|
||||
- **[File manager]** Add trim option for text fields of storages collection ([#6604](https://github.com/nocobase/nocobase/pull/6604)) by @mytharcher
|
||||
|
||||
- **[Workflow]** Improve code ([#6589](https://github.com/nocobase/nocobase/pull/6589)) by @mytharcher
|
||||
|
||||
- **[Workflow: Approval]** Support to use block template for approval process form by @mytharcher
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[database]** Avoid "datetimeNoTz" field changes when value not changed in updating record ([#6588](https://github.com/nocobase/nocobase/pull/6588)) by @mytharcher
|
||||
|
||||
- **[client]**
|
||||
- association field (select) displaying N/A when exposing related collection fields ([#6582](https://github.com/nocobase/nocobase/pull/6582)) by @katherinehhh
|
||||
|
||||
- Fix `disabled` property not works when `SchemaInitializerItem` has `items` ([#6597](https://github.com/nocobase/nocobase/pull/6597)) by @mytharcher
|
||||
|
||||
- cascade issue: 'The value of xxx cannot be in array format' when deleting and re-selecting ([#6585](https://github.com/nocobase/nocobase/pull/6585)) by @katherinehhh
|
||||
|
||||
- **[Collection field: Many to many (array)]** Issue of filtering by fields in an association collection with a many to many (array) field ([#6596](https://github.com/nocobase/nocobase/pull/6596)) by @2013xile
|
||||
|
||||
- **[Public forms]** View permissions include list and get ([#6607](https://github.com/nocobase/nocobase/pull/6607)) by @chenos
|
||||
|
||||
- **[Authentication]** token assignment in `AuthProvider` ([#6593](https://github.com/nocobase/nocobase/pull/6593)) by @2013xile
|
||||
|
||||
- **[Workflow]** Fix sync option display incorrectly ([#6595](https://github.com/nocobase/nocobase/pull/6595)) by @mytharcher
|
||||
|
||||
- **[Block: Map]** map management validation should not pass with space input ([#6575](https://github.com/nocobase/nocobase/pull/6575)) by @katherinehhh
|
||||
|
||||
- **[Workflow: Approval]**
|
||||
- Fix client variables to use in approval form by @mytharcher
|
||||
|
||||
- Fix branch mode when `endOnReject` configured as `true` by @mytharcher
|
||||
|
||||
## [v1.6.14](https://github.com/nocobase/nocobase/compare/v1.6.13...v1.6.14) - 2025-03-29
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
@ -5,6 +5,58 @@
|
||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
||||
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
|
||||
|
||||
## [v1.6.16](https://github.com/nocobase/nocobase/compare/v1.6.15...v1.6.16) - 2025-04-03
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 表单字段设置不可编辑不起作用 ([#6610](https://github.com/nocobase/nocobase/pull/6610)) by @katherinehhh
|
||||
|
||||
- 表单字段标题因冒号导致的截断问题 ([#6599](https://github.com/nocobase/nocobase/pull/6599)) by @katherinehhh
|
||||
|
||||
- **[database]** 删除一对多记录时,同时传递 `filter` 和 `filterByTk` 参数,`filter` 包含关系字段时,`filterByTk` 参数失效 ([#6606](https://github.com/nocobase/nocobase/pull/6606)) by @2013xile
|
||||
|
||||
## [v1.6.15](https://github.com/nocobase/nocobase/compare/v1.6.14...v1.6.15) - 2025-04-01
|
||||
|
||||
### 🚀 优化
|
||||
|
||||
- **[database]**
|
||||
- 为多行文本类型字段增加去除首尾空白字符的选项 ([#6603](https://github.com/nocobase/nocobase/pull/6603)) by @mytharcher
|
||||
|
||||
- 为单行文本增加自动去除首尾空白字符的选项 ([#6565](https://github.com/nocobase/nocobase/pull/6565)) by @mytharcher
|
||||
|
||||
- **[文件管理器]** 为存储引擎表的文本字段增加去除首尾空白字符的选项 ([#6604](https://github.com/nocobase/nocobase/pull/6604)) by @mytharcher
|
||||
|
||||
- **[工作流]** 优化代码 ([#6589](https://github.com/nocobase/nocobase/pull/6589)) by @mytharcher
|
||||
|
||||
- **[工作流:审批]** 支持审批表单使用区块模板 by @mytharcher
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[database]** 避免“日期时间(无时区)”字段在值未变动的更新时触发值改变 ([#6588](https://github.com/nocobase/nocobase/pull/6588)) by @mytharcher
|
||||
|
||||
- **[client]**
|
||||
- 关系字段(select)放出关系表字段时默认显示 N/A ([#6582](https://github.com/nocobase/nocobase/pull/6582)) by @katherinehhh
|
||||
|
||||
- 修复 `SchemaInitializerItem` 配置了 `items` 时 `disabled` 属性无效的问题 ([#6597](https://github.com/nocobase/nocobase/pull/6597)) by @mytharcher
|
||||
|
||||
- 级联组件删除后重新选择时出现 'The value of xxx cannot be in array format' ([#6585](https://github.com/nocobase/nocobase/pull/6585)) by @katherinehhh
|
||||
|
||||
- **[数据表字段:多对多 (数组)]** 主表筛选带有多对多(数组)字段的关联表中的字段报错的问题 ([#6596](https://github.com/nocobase/nocobase/pull/6596)) by @2013xile
|
||||
|
||||
- **[公开表单]** 查看权限包括 list 和 get ([#6607](https://github.com/nocobase/nocobase/pull/6607)) by @chenos
|
||||
|
||||
- **[用户认证]** `AuthProvider` 中的 token 赋值 ([#6593](https://github.com/nocobase/nocobase/pull/6593)) by @2013xile
|
||||
|
||||
- **[工作流]** 修复同步选项展示问题 ([#6595](https://github.com/nocobase/nocobase/pull/6595)) by @mytharcher
|
||||
|
||||
- **[区块:地图]** 地图管理必填校验不应通过空格输入 ([#6575](https://github.com/nocobase/nocobase/pull/6575)) by @katherinehhh
|
||||
|
||||
- **[工作流:审批]**
|
||||
- 修复审批表单中的前端变量 by @mytharcher
|
||||
|
||||
- 修复分支模式下配置拒绝则结束时的流程问题 by @mytharcher
|
||||
|
||||
## [v1.6.14](https://github.com/nocobase/nocobase/compare/v1.6.13...v1.6.14) - 2025-03-29
|
||||
|
||||
### 🐛 修复
|
||||
|
@ -1,4 +1,4 @@
|
||||
Updated Date: February 20, 2025
|
||||
Updated Date: April 1, 2025
|
||||
|
||||
NocoBase License Agreement
|
||||
|
||||
@ -88,7 +88,7 @@ Except for Third-Party Open Source Software, the Company owns all copyrights, tr
|
||||
|
||||
6.6 Can sell plugins developed for Software in the Marketplace.
|
||||
|
||||
6.7 The User with an Enterprise Edition License can sell Upper Layer Application to their clients.
|
||||
6.7 The User with a Professional or Enterprise Edition License can sell Upper Layer Application to their clients.
|
||||
|
||||
6.8 Not restricted by the AGPL-3.0 agreement.
|
||||
|
||||
@ -106,9 +106,9 @@ Except for Third-Party Open Source Software, the Company owns all copyrights, tr
|
||||
|
||||
7.4 It is not allowed to provide any form of no-code, zero-code, low-code platform SaaS products to the public using the original or modified Software.
|
||||
|
||||
7.5 It is not allowed for the User withot an Enterprise Edition license to sell Upper Layer Application to clients without a Commercial license.
|
||||
7.5 It is not allowed for the User withot a Professional or Enterprise Edition license to sell Upper Layer Application to clients without a Commercial license.
|
||||
|
||||
7.6 It is not allowed for the User with an Enterprise Edition license to sell Upper Layer Application to clients without a Commercial license with access to further development and configuration.
|
||||
7.6 It is not allowed for the User with a Professional or Enterprise Edition license to sell Upper Layer Application to clients without a Commercial license with access to further development and configuration.
|
||||
|
||||
7.7 It is not allowed to publicly sell plugins developed for Software outside of the Marketplace.
|
||||
|
||||
|
@ -63,7 +63,7 @@ export const SchemaInitializerItem = memo(
|
||||
className: className,
|
||||
label: children || compile(title),
|
||||
onClick: (info) => {
|
||||
if (info.key !== name) return;
|
||||
if (disabled || info.key !== name) return;
|
||||
if (closeInitializerMenuWhenClick) {
|
||||
setVisible?.(false);
|
||||
}
|
||||
@ -73,10 +73,10 @@ export const SchemaInitializerItem = memo(
|
||||
children: childrenItems,
|
||||
},
|
||||
];
|
||||
}, [name, style, className, children, title, onClick, icon, childrenItems]);
|
||||
}, [name, disabled, style, className, children, title, onClick, icon, childrenItems]);
|
||||
|
||||
if (items && items.length > 0) {
|
||||
return <SchemaInitializerMenu items={menuItems}></SchemaInitializerMenu>;
|
||||
return <SchemaInitializerMenu disabled={disabled} items={menuItems}></SchemaInitializerMenu>;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
|
@ -31,6 +31,12 @@ export class TextareaFieldInterface extends CollectionFieldInterface {
|
||||
titleUsable = true;
|
||||
properties = {
|
||||
...defaultProps,
|
||||
trim: {
|
||||
type: 'boolean',
|
||||
'x-content': '{{t("Automatically remove heading and tailing spaces")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
},
|
||||
};
|
||||
schemaInitialize(schema: ISchema, { block }) {
|
||||
if (['Table', 'Kanban'].includes(block)) {
|
||||
|
@ -104,12 +104,31 @@ const CollectionFieldInternalField = (props) => {
|
||||
const dynamicProps = useDynamicComponentProps(uiSchema?.['x-use-component-props'], props);
|
||||
|
||||
useEffect(() => {
|
||||
// There seems to be a bug in formily where after setting a field to readPretty, switching to editable,
|
||||
// then back to readPretty, and refreshing the page, the field remains in editable state. The expected state is readPretty.
|
||||
// This code is meant to fix this issue.
|
||||
/**
|
||||
* There seems to be a bug in formily where after setting a field to readPretty, switching to editable,
|
||||
* then back to readPretty, and refreshing the page, the field remains in editable state. The expected state is readPretty.
|
||||
* This code is meant to fix this issue.
|
||||
*/
|
||||
if (fieldSchema['x-read-pretty'] === true && !field.readPretty) {
|
||||
field.readPretty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This solves the issue: After creating a form and setting a field to "read-only", the field remains editable when refreshing the page and reopening the dialog.
|
||||
*
|
||||
* Note: This might be a bug in Formily
|
||||
* When both x-disabled and x-read-pretty exist in the Schema:
|
||||
* - If x-disabled appears before x-read-pretty in the Schema JSON, the disabled state becomes ineffective
|
||||
* - The reason is that during field instance initialization, field.disabled is set before field.readPretty, which causes the pattern value to be changed to 'editable'
|
||||
* - This issue is related to the order of JSON fields, which might return different orders in different environments (databases), thus making the issue inconsistent to reproduce
|
||||
*
|
||||
* Reference to Formily source code:
|
||||
* 1. Setting readPretty may cause pattern to be changed to 'editable': https://github.com/alibaba/formily/blob/d4bb96c40e7918210b1bd7d57b8fadee0cfe4b26/packages/core/src/models/BaseField.ts#L208-L224
|
||||
* 2. The execution order of the each method depends on the order of JSON fields: https://github.com/alibaba/formily/blob/123d536b6076196e00b4e02ee160d72480359f54/packages/json-schema/src/schema.ts#L486-L519
|
||||
*/
|
||||
if (fieldSchema['x-disabled'] === true) {
|
||||
field.disabled = true;
|
||||
}
|
||||
field.data = field.data || {};
|
||||
field.data.dataSource = uiSchema?.enum;
|
||||
}, [field, fieldSchema]);
|
||||
|
@ -107,7 +107,7 @@ AssociationFilter.BlockDesigner = AssociationFilterBlockDesigner;
|
||||
AssociationFilter.useAssociationField = () => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const collection = useCollection();
|
||||
return React.useMemo(() => collection.getField(fieldSchema.name as any), [fieldSchema.name]);
|
||||
return React.useMemo(() => collection?.getField(fieldSchema?.name as any), [fieldSchema?.name]);
|
||||
};
|
||||
|
||||
export class AssociationFilterPlugin extends Plugin {
|
||||
|
@ -45,16 +45,16 @@ describe('CollectionSelect', () => {
|
||||
expect(container).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
<div
|
||||
class="css-dev-only-do-not-override-1rquknz ant-app"
|
||||
class="css-dev-only-do-not-override-qu8jc9 ant-app"
|
||||
style="height: 100%;"
|
||||
>
|
||||
<div
|
||||
aria-label="block-item-demo title"
|
||||
class="nb-block-item nb-form-item css-9qorhu ant-nb-block-item css-dev-only-do-not-override-1rquknz"
|
||||
class="nb-block-item nb-form-item css-9qorhu ant-nb-block-item css-dev-only-do-not-override-qu8jc9"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="css-1yh5po ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-1rquknz"
|
||||
class="css-vij405 ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-qu8jc9"
|
||||
>
|
||||
<div
|
||||
class="ant-formily-item-label"
|
||||
@ -84,7 +84,7 @@ describe('CollectionSelect', () => {
|
||||
class="ant-formily-item-control-content-component"
|
||||
>
|
||||
<div
|
||||
class="ant-select ant-select-outlined css-dev-only-do-not-override-1rquknz ant-select-focused ant-select-single ant-select-show-arrow ant-select-show-search"
|
||||
class="ant-select css-dev-only-do-not-override-qu8jc9 ant-select-focused ant-select-single ant-select-show-arrow ant-select-show-search"
|
||||
data-testid="select-collection"
|
||||
role="button"
|
||||
>
|
||||
@ -96,9 +96,6 @@ describe('CollectionSelect', () => {
|
||||
</span>
|
||||
<div
|
||||
class="ant-select-selector"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-wrap"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-search"
|
||||
@ -123,7 +120,6 @@ describe('CollectionSelect', () => {
|
||||
>
|
||||
Users
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -186,16 +182,16 @@ describe('CollectionSelect', () => {
|
||||
expect(container).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
<div
|
||||
class="css-dev-only-do-not-override-1rquknz ant-app"
|
||||
class="css-dev-only-do-not-override-qu8jc9 ant-app"
|
||||
style="height: 100%;"
|
||||
>
|
||||
<div
|
||||
aria-label="block-item-demo title"
|
||||
class="nb-block-item nb-form-item css-9qorhu ant-nb-block-item css-dev-only-do-not-override-1rquknz"
|
||||
class="nb-block-item nb-form-item css-9qorhu ant-nb-block-item css-dev-only-do-not-override-qu8jc9"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="css-1yh5po ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-1rquknz"
|
||||
class="css-vij405 ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-qu8jc9"
|
||||
>
|
||||
<div
|
||||
class="ant-formily-item-label"
|
||||
@ -226,7 +222,7 @@ describe('CollectionSelect', () => {
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
class="ant-tag css-dev-only-do-not-override-1rquknz"
|
||||
class="ant-tag css-dev-only-do-not-override-qu8jc9"
|
||||
>
|
||||
Users
|
||||
</span>
|
||||
|
@ -37,6 +37,15 @@ const formItemWrapCss = css`
|
||||
.ant-description-textarea img {
|
||||
max-width: 100%;
|
||||
}
|
||||
&.ant-formily-item-layout-vertical .ant-formily-item-label {
|
||||
display: inline;
|
||||
.ant-formily-item-label-tooltip-icon {
|
||||
display: inline;
|
||||
}
|
||||
.ant-formily-item-label-content {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const formItemLabelCss = css`
|
||||
@ -44,7 +53,7 @@ const formItemLabelCss = css`
|
||||
padding: 0px !important;
|
||||
}
|
||||
> .ant-formily-item-label {
|
||||
display: none;
|
||||
display: none !important;
|
||||
}
|
||||
`;
|
||||
|
||||
@ -83,7 +92,7 @@ export const FormItem: any = withDynamicSchemaProps(
|
||||
[formItemLabelCss]: showTitle === false,
|
||||
});
|
||||
}, [showTitle]);
|
||||
|
||||
console.log(className);
|
||||
// 联动规则中的“隐藏保留值”的效果
|
||||
if (field.data?.hidden) {
|
||||
return null;
|
||||
|
@ -55,4 +55,18 @@ describe('text field', () => {
|
||||
});
|
||||
await Test.sync();
|
||||
});
|
||||
|
||||
it('trim', async () => {
|
||||
const collection = db.collection({
|
||||
name: 'tests',
|
||||
fields: [{ type: 'text', name: 'name', trim: true }],
|
||||
});
|
||||
await db.sync();
|
||||
const model = await collection.model.create({
|
||||
name: ' n1\n ',
|
||||
});
|
||||
expect(model.toJSON()).toMatchObject({
|
||||
name: 'n1',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -159,6 +159,7 @@ describe('has many repository', () => {
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{ type: 'string', name: 'title' },
|
||||
{ type: 'belongsTo', name: 'user' },
|
||||
{ type: 'belongsToMany', name: 'tags', through: 'posts_tags' },
|
||||
{ type: 'hasMany', name: 'comments' },
|
||||
{ type: 'string', name: 'status' },
|
||||
@ -480,6 +481,51 @@ describe('has many repository', () => {
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
test('destroy by pk and filter with association', async () => {
|
||||
const u1 = await User.repository.create({
|
||||
values: { name: 'u1' },
|
||||
});
|
||||
|
||||
const UserPostRepository = new HasManyRepository(User, 'posts', u1.id);
|
||||
|
||||
const p1 = await UserPostRepository.create({
|
||||
values: {
|
||||
title: 't1',
|
||||
status: 'published',
|
||||
user: u1,
|
||||
},
|
||||
});
|
||||
|
||||
const p2 = await UserPostRepository.create({
|
||||
values: {
|
||||
title: 't2',
|
||||
status: 'draft',
|
||||
user: u1,
|
||||
},
|
||||
});
|
||||
|
||||
await UserPostRepository.destroy({
|
||||
filterByTk: p1.id,
|
||||
filter: {
|
||||
user: {
|
||||
id: u1.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
await UserPostRepository.findOne({
|
||||
filterByTk: p1.id,
|
||||
}),
|
||||
).toBeNull();
|
||||
|
||||
expect(
|
||||
await UserPostRepository.findOne({
|
||||
filterByTk: p2.id,
|
||||
}),
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
test('destroy by pk', async () => {
|
||||
const u1 = await User.repository.create({
|
||||
values: { name: 'u1' },
|
||||
|
@ -29,13 +29,12 @@ export class DatetimeNoTzField extends Field {
|
||||
return DatetimeNoTzTypeMySQL;
|
||||
}
|
||||
|
||||
return DataTypes.STRING;
|
||||
return DataTypes.DATE;
|
||||
}
|
||||
|
||||
init() {
|
||||
beforeSave = async (instance, options) => {
|
||||
const { name, defaultToCurrentTime, onUpdateToCurrentTime } = this.options;
|
||||
|
||||
this.beforeSave = async (instance, options) => {
|
||||
const value = instance.get(name);
|
||||
|
||||
if (!value && instance.isNewRecord && defaultToCurrentTime) {
|
||||
@ -48,7 +47,6 @@ export class DatetimeNoTzField extends Field {
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
additionalSequelizeOptions(): {} {
|
||||
const { name } = this.options;
|
||||
@ -57,17 +55,14 @@ export class DatetimeNoTzField extends Field {
|
||||
const timezone = this.database.options.rawTimezone || '+00:00';
|
||||
|
||||
const isPg = this.database.inDialect('postgres');
|
||||
const isMySQLCompatibleDialect = this.database.isMySQLCompatibleDialect();
|
||||
|
||||
return {
|
||||
get() {
|
||||
const val = this.getDataValue(name);
|
||||
|
||||
if (val instanceof Date) {
|
||||
if (isPg) {
|
||||
return moment(val).format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
// format to YYYY-MM-DD HH:mm:ss
|
||||
const momentVal = moment(val).utcOffset(timezone);
|
||||
const momentVal = moment(val);
|
||||
return momentVal.format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
|
||||
@ -75,18 +70,24 @@ export class DatetimeNoTzField extends Field {
|
||||
},
|
||||
|
||||
set(val) {
|
||||
if (typeof val === 'string' && isIso8601(val)) {
|
||||
const momentVal = moment(val).utcOffset(timezone);
|
||||
val = momentVal.format('YYYY-MM-DD HH:mm:ss');
|
||||
if (val == null) {
|
||||
return this.setDataValue(name, null);
|
||||
}
|
||||
|
||||
if (val && val instanceof Date) {
|
||||
// format to YYYY-MM-DD HH:mm:ss
|
||||
const momentVal = moment(val).utcOffset(timezone);
|
||||
val = momentVal.format('YYYY-MM-DD HH:mm:ss');
|
||||
const dateOffset = new Date().getTimezoneOffset();
|
||||
const momentVal = moment(val);
|
||||
if ((typeof val === 'string' && isIso8601(val)) || val instanceof Date) {
|
||||
momentVal.utcOffset(timezone);
|
||||
momentVal.utcOffset(-dateOffset, true);
|
||||
}
|
||||
|
||||
return this.setDataValue(name, val);
|
||||
if (isMySQLCompatibleDialect) {
|
||||
momentVal.millisecond(0);
|
||||
}
|
||||
|
||||
const date = momentVal.toDate();
|
||||
|
||||
return this.setDataValue(name, date);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -23,9 +23,20 @@ export class TextField extends Field {
|
||||
this.options.defaultValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
additionalSequelizeOptions() {
|
||||
const { name, trim } = this.options;
|
||||
|
||||
return {
|
||||
set(value) {
|
||||
this.setDataValue(name, trim ? value?.trim() : value);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface TextFieldOptions extends BaseColumnFieldOptions {
|
||||
type: 'text';
|
||||
length?: 'tiny' | 'medium' | 'long';
|
||||
trim?: boolean;
|
||||
}
|
||||
|
@ -72,7 +72,13 @@ export class HasManyRepository extends MultipleRelationRepository {
|
||||
const filterResult = this.parseFilter(options['filter'], options);
|
||||
|
||||
if (filterResult.include && filterResult.include.length > 0) {
|
||||
return await this.destroyByFilter(options['filter'], transaction);
|
||||
return await this.destroyByFilter(
|
||||
{
|
||||
filter: options['filter'],
|
||||
filterByTk: options['filterByTk'],
|
||||
},
|
||||
transaction,
|
||||
);
|
||||
}
|
||||
|
||||
where.push(filterResult.where);
|
||||
|
@ -179,9 +179,15 @@ export abstract class MultipleRelationRepository extends RelationRepository {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected async destroyByFilter(filter: Filter, transaction?: Transaction) {
|
||||
protected async destroyByFilter(
|
||||
options: {
|
||||
filter?: Filter;
|
||||
filterByTk?: TargetKey | TargetKey[];
|
||||
},
|
||||
transaction?: Transaction,
|
||||
) {
|
||||
const instances = await this.find({
|
||||
filter: filter,
|
||||
...options,
|
||||
transaction,
|
||||
});
|
||||
|
||||
|
@ -21,6 +21,7 @@ export default defineCollection({
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
translation: true,
|
||||
trim: true,
|
||||
},
|
||||
{
|
||||
title: '英文标识',
|
||||
@ -28,6 +29,7 @@ export default defineCollection({
|
||||
type: 'uid',
|
||||
name: 'name',
|
||||
unique: true,
|
||||
trim: true,
|
||||
},
|
||||
{
|
||||
comment: '类型标识,如 local/ali-oss 等',
|
||||
@ -51,12 +53,14 @@ export default defineCollection({
|
||||
type: 'text',
|
||||
name: 'path',
|
||||
defaultValue: '',
|
||||
trim: true,
|
||||
},
|
||||
{
|
||||
comment: '访问地址前缀',
|
||||
type: 'string',
|
||||
name: 'baseUrl',
|
||||
defaultValue: '',
|
||||
trim: true,
|
||||
},
|
||||
// TODO(feature): 需要使用一个实现了可设置默认值的字段
|
||||
{
|
||||
|
@ -147,6 +147,7 @@ export function AdminPublicFormPage() {
|
||||
}}
|
||||
>
|
||||
<Breadcrumb
|
||||
style={{ marginLeft: '10px' }}
|
||||
items={[
|
||||
{
|
||||
title: <Link to={`/admin/settings/public-forms`}>{t('Public forms', { ns: NAMESPACE })}</Link>,
|
||||
|
@ -33,7 +33,7 @@ import {
|
||||
import { Input, Modal, Spin } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { isDesktop } from 'react-device-detect';
|
||||
import { isDesktop, isMobile } from 'react-device-detect';
|
||||
import { useParams } from 'react-router';
|
||||
import { usePublicSubmitActionProps } from '../hooks';
|
||||
import { UnEnabledFormPlaceholder, UnFoundFormPlaceholder } from './UnEnabledFormPlaceholder';
|
||||
@ -129,9 +129,6 @@ const PublicFormMessageProvider = ({ children }) => {
|
||||
</PublicFormMessageContext.Provider>
|
||||
);
|
||||
};
|
||||
function isMobile() {
|
||||
return window.matchMedia('(max-width: 768px)').matches;
|
||||
}
|
||||
|
||||
const AssociationFieldMobile = (props) => {
|
||||
return <AssociationField {...props} popupMatchSelectWidth={true} />;
|
||||
@ -165,7 +162,6 @@ const mobileComponents = {
|
||||
function InternalPublicForm() {
|
||||
const params = useParams();
|
||||
const apiClient = useAPIClient();
|
||||
const isMobileMedia = isMobile();
|
||||
const { error, data, loading, run } = useRequest<any>(
|
||||
{
|
||||
url: `publicForms:getMeta/${params.name}`,
|
||||
@ -243,7 +239,7 @@ function InternalPublicForm() {
|
||||
if (!data?.data) {
|
||||
return <UnEnabledFormPlaceholder />;
|
||||
}
|
||||
const components = isMobileMedia ? mobileComponents : {};
|
||||
const components = isMobile ? mobileComponents : {};
|
||||
return (
|
||||
<ACLCustomContext.Provider value={{ allowAll: true }}>
|
||||
<PublicAPIClientProvider>
|
||||
|
@ -172,7 +172,7 @@ export class PluginPublicFormsServer extends Plugin {
|
||||
skip: true,
|
||||
};
|
||||
} else if (
|
||||
(actionName === 'list' && ctx.PublicForm['targetCollections'].includes(resourceName)) ||
|
||||
(['list', 'get'].includes(actionName) && ctx.PublicForm['targetCollections'].includes(resourceName)) ||
|
||||
(collection?.options.template === 'file' && actionName === 'create') ||
|
||||
(resourceName === 'storages' && ['getBasicInfo', 'createPresignedUrl'].includes(actionName)) ||
|
||||
(resourceName === 'vditor' && ['check'].includes(actionName)) ||
|
||||
|
@ -189,7 +189,7 @@ export class ApprovalPassthroughModeNode {
|
||||
this.detailsConfigureFieldsButton = page.getByLabel(
|
||||
`schema-initializer-Grid-details:configureFields-${collectionName}`,
|
||||
);
|
||||
this.addActionsMenu = page.getByRole('menuitem', { name: 'Process form' }).getByRole('switch');
|
||||
this.addActionsMenu = page.getByRole('menuitem', { name: 'Process form' });
|
||||
this.actionsConfigureFieldsButton = page.getByLabel('schema-initializer-Grid-FormItemInitializers-approvalRecords');
|
||||
this.actionsConfigureActionsButton = page.getByLabel(
|
||||
'schema-initializer-ActionBar-ApprovalProcessAddActionButton-',
|
||||
@ -262,7 +262,7 @@ export class ApprovalBranchModeNode {
|
||||
this.detailsConfigureFieldsButton = page.getByLabel(
|
||||
`schema-initializer-Grid-details:configureFields-${collectionName}`,
|
||||
);
|
||||
this.addActionsMenu = page.getByRole('menuitem', { name: 'Process form' }).getByRole('switch');
|
||||
this.addActionsMenu = page.getByRole('menuitem', { name: 'Process form' });
|
||||
this.actionsConfigureFieldsButton = page.getByLabel('schema-initializer-Grid-FormItemInitializers-approvalRecords');
|
||||
this.actionsConfigureActionsButton = page.getByLabel('schema-initializer-ActionBar-');
|
||||
this.addApproveButton = page.getByRole('menuitem', { name: 'Approve' }).getByRole('switch');
|
||||
|
@ -359,6 +359,50 @@ describe('workflow > triggers > collection', () => {
|
||||
const executions = await workflow.getExecutions();
|
||||
expect(executions.length).toBe(1);
|
||||
});
|
||||
|
||||
it('datetime field not changed', async () => {
|
||||
const workflow = await WorkflowModel.create({
|
||||
enabled: true,
|
||||
sync: true,
|
||||
type: 'collection',
|
||||
config: {
|
||||
mode: 2,
|
||||
collection: 'posts',
|
||||
changed: ['createdAt'],
|
||||
},
|
||||
});
|
||||
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
await PostRepo.update({ filterByTk: post.id, values: { ...post.get(), title: 't2' } });
|
||||
|
||||
const executions = await workflow.getExecutions();
|
||||
expect(executions.length).toBe(0);
|
||||
});
|
||||
|
||||
it('datetimeNoTz field not changed', async () => {
|
||||
db.getCollection('posts').addField('dateOnly', {
|
||||
type: 'datetimeNoTz',
|
||||
});
|
||||
|
||||
await db.sync();
|
||||
|
||||
const workflow = await WorkflowModel.create({
|
||||
enabled: true,
|
||||
sync: true,
|
||||
type: 'collection',
|
||||
config: {
|
||||
mode: 2,
|
||||
collection: 'posts',
|
||||
changed: ['dateOnly'],
|
||||
},
|
||||
});
|
||||
|
||||
const post = await PostRepo.create({ values: { title: 't1', dateOnly: '2020-01-01 00:00:00' } });
|
||||
await PostRepo.update({ filterByTk: post.id, values: { ...post.get(), title: 't2' } });
|
||||
|
||||
const executions = await workflow.getExecutions();
|
||||
expect(executions.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('config.condition', () => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user