@nocobase/client-v2 2.2.0-beta.9 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/es/APIClient.d.ts +13 -3
  2. package/es/BaseApplication.d.ts +1 -0
  3. package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
  4. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
  5. package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
  6. package/es/components/category-tabs/index.d.ts +9 -0
  7. package/es/components/form/ScanInput/useCodeScanner.d.ts +3 -2
  8. package/es/components/form/TypedVariableInput.d.ts +31 -5
  9. package/es/components/form/filter/CollectionFilter.d.ts +2 -0
  10. package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
  11. package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
  12. package/es/components/form/filter/index.d.ts +2 -0
  13. package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
  14. package/es/components/index.d.ts +1 -0
  15. package/es/flow/actions/index.d.ts +1 -1
  16. package/es/flow/actions/linkageRules.d.ts +27 -0
  17. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
  18. package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
  19. package/es/flow/components/FieldAssignValueInput.d.ts +7 -24
  20. package/es/flow/components/FlowRoute.d.ts +2 -2
  21. package/es/flow/components/RunJSValueEditor.d.ts +1 -0
  22. package/es/flow/components/field-value-variable/DateVariableEditor.d.ts +24 -0
  23. package/es/flow/components/field-value-variable/FieldValueVariableInput.d.ts +31 -0
  24. package/es/flow/components/field-value-variable/dateValue.d.ts +36 -0
  25. package/es/flow/components/field-value-variable/index.d.ts +11 -0
  26. package/es/flow/components/filter/FilterGroup.d.ts +1 -0
  27. package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
  28. package/es/flow/components/placeholders/BlockPlaceholder.d.ts +1 -0
  29. package/es/flow/index.d.ts +1 -0
  30. package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
  31. package/es/flow/internal/utils/operatorSchemaHelper.d.ts +2 -2
  32. package/es/flow/models/actions/UpdateRecordActionUtils.d.ts +8 -2
  33. package/es/flow/models/base/ActionModelCore.d.ts +4 -2
  34. package/es/flow/models/base/BlockGridModel.d.ts +3 -0
  35. package/es/flow/models/base/CollectionBlockModel.d.ts +3 -0
  36. package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
  37. package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
  38. package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
  39. package/es/flow/models/blocks/details/DetailsBlockModel.d.ts +3 -0
  40. package/es/flow/models/blocks/filter-form/FilterFormGridModel.d.ts +1 -0
  41. package/es/flow/models/blocks/filter-form/FilterFormSubmitActionModel.d.ts +3 -1
  42. package/es/flow/models/blocks/form/FormBlockModel.d.ts +7 -0
  43. package/es/flow/models/blocks/form/FormGridModel.d.ts +6 -0
  44. package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
  45. package/es/flow/models/blocks/form/value-runtime/rules.d.ts +1 -0
  46. package/es/flow/models/blocks/table/JSColumnModel.d.ts +2 -0
  47. package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
  48. package/es/flow/models/blocks/table/TableColumnModel.d.ts +12 -0
  49. package/es/flow/models/blocks/table/utils.d.ts +1 -0
  50. package/es/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.d.ts +2 -1
  51. package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +0 -17
  52. package/es/flow/models/fields/AssociationFieldModel/recordSelectShared.d.ts +41 -0
  53. package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
  54. package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
  55. package/es/flow-compat/passwordUtils.d.ts +1 -1
  56. package/es/flow-compat/routeTypes.d.ts +1 -0
  57. package/es/index.d.ts +5 -0
  58. package/es/index.mjs +199 -161
  59. package/es/ui-operation/index.d.ts +15 -0
  60. package/es/ui-operation/ui-operation-codec.d.ts +10 -0
  61. package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
  62. package/es/utils/index.d.ts +1 -0
  63. package/lib/index.js +215 -177
  64. package/lib/locale/languageCodes.js +3 -1
  65. package/package.json +9 -7
  66. package/src/APIClient.ts +90 -7
  67. package/src/Application.tsx +7 -1
  68. package/src/BaseApplication.tsx +11 -6
  69. package/src/__tests__/APIClient.test.tsx +58 -0
  70. package/src/__tests__/app.test.tsx +73 -0
  71. package/src/__tests__/browserChecker.test.ts +61 -0
  72. package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
  73. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +35 -2
  74. package/src/__tests__/settings-center.test.tsx +270 -6
  75. package/src/acl/ACLProvider.tsx +2 -0
  76. package/src/acl/__tests__/ACLProvider.test.tsx +92 -0
  77. package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
  78. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
  79. package/src/collection-manager/field-validation.ts +1 -1
  80. package/src/components/README.md +7 -1
  81. package/src/components/README.zh-CN.md +6 -1
  82. package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
  83. package/src/components/category-tabs/index.ts +10 -0
  84. package/src/components/form/JsonTextArea.tsx +4 -0
  85. package/src/components/form/ScanInput/CodeScanner.tsx +79 -35
  86. package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
  87. package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +284 -11
  88. package/src/components/form/ScanInput/useCodeScanner.ts +332 -24
  89. package/src/components/form/TypedVariableInput.tsx +452 -101
  90. package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
  91. package/src/components/form/filter/CollectionFilter.tsx +4 -0
  92. package/src/components/form/filter/CollectionFilterItem.tsx +36 -9
  93. package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
  94. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +55 -0
  95. package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
  96. package/src/components/form/filter/index.ts +2 -0
  97. package/src/components/form/filter/useFilterActionProps.ts +37 -6
  98. package/src/components/form/table/Table.tsx +81 -6
  99. package/src/components/form/table/__tests__/Table.columnWidth.test.tsx +433 -0
  100. package/src/components/index.ts +1 -0
  101. package/src/flow/FlowPage.tsx +9 -1
  102. package/src/flow/__tests__/FlowPage.test.tsx +50 -3
  103. package/src/flow/__tests__/FlowRoute.test.tsx +128 -10
  104. package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
  105. package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
  106. package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -0
  107. package/src/flow/actions/__tests__/linkageRules.actionStates.test.ts +33 -0
  108. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +72 -0
  109. package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
  110. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
  111. package/src/flow/actions/index.ts +2 -0
  112. package/src/flow/actions/linkageRules.tsx +144 -26
  113. package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
  114. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +0 -1
  115. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +78 -59
  116. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +253 -6
  117. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +228 -0
  118. package/src/flow/common/Markdown/style.ts +4 -0
  119. package/src/flow/components/BlockItemCard.tsx +2 -2
  120. package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
  121. package/src/flow/components/FieldAssignValueInput.tsx +72 -584
  122. package/src/flow/components/FlowRoute.tsx +78 -32
  123. package/src/flow/components/RunJSValueEditor.tsx +9 -1
  124. package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
  125. package/src/flow/components/field-value-variable/DateVariableEditor.tsx +181 -0
  126. package/src/flow/components/field-value-variable/FieldValueVariableInput.tsx +326 -0
  127. package/src/flow/components/field-value-variable/__tests__/FieldValueVariableInput.test.tsx +380 -0
  128. package/src/flow/components/field-value-variable/dateValue.ts +223 -0
  129. package/src/flow/components/field-value-variable/index.ts +12 -0
  130. package/src/flow/components/filter/FilterGroup.tsx +33 -5
  131. package/src/flow/components/filter/VariableFilterItem.tsx +170 -13
  132. package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
  133. package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
  134. package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +195 -1
  135. package/src/flow/components/placeholders/BlockPlaceholder.tsx +70 -23
  136. package/src/flow/components/placeholders/__tests__/BlockPlaceholder.test.tsx +57 -7
  137. package/src/flow/index.ts +3 -0
  138. package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
  139. package/src/flow/internal/utils/operatorSchemaHelper.ts +3 -2
  140. package/src/flow/models/actions/UpdateRecordActionModel.tsx +18 -1
  141. package/src/flow/models/actions/UpdateRecordActionUtils.ts +18 -6
  142. package/src/flow/models/actions/__tests__/UpdateRecordActionModel.test.ts +76 -4
  143. package/src/flow/models/base/ActionModelCore.tsx +11 -2
  144. package/src/flow/models/base/BlockGridModel.tsx +26 -0
  145. package/src/flow/models/base/CollectionBlockModel.tsx +38 -3
  146. package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
  147. package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
  148. package/src/flow/models/base/PageModel/PageTabModel.tsx +249 -18
  149. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +790 -10
  150. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
  151. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
  152. package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +1075 -7
  153. package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +49 -0
  154. package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
  155. package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
  156. package/src/flow/models/base/__tests__/CollectionBlockModel.initialBeforeRenderRefresh.test.ts +125 -9
  157. package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +28 -53
  158. package/src/flow/models/blocks/details/DetailsBlockModel.tsx +8 -3
  159. package/src/flow/models/blocks/details/__tests__/DetailsBlockModel.initialPaginationChangeRefresh.test.ts +32 -1
  160. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +28 -3
  161. package/src/flow/models/blocks/filter-form/FilterFormGridModel.tsx +36 -5
  162. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +155 -25
  163. package/src/flow/models/blocks/filter-form/FilterFormSubmitActionModel.tsx +57 -4
  164. package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.onModelCreated.test.ts +174 -1
  165. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +395 -1
  166. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.getFilterValue.test.ts +72 -0
  167. package/src/flow/models/blocks/filter-form/__tests__/FilterFormSubmitActionModel.lifecycle.test.ts +90 -0
  168. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +66 -4
  169. package/src/flow/models/blocks/filter-manager/FilterManager.ts +5 -0
  170. package/src/flow/models/blocks/filter-manager/__tests__/FilterManager.test.ts +40 -0
  171. package/src/flow/models/blocks/form/FormBlockModel.tsx +47 -0
  172. package/src/flow/models/blocks/form/FormGridModel.tsx +4 -0
  173. package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
  174. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +8 -2
  175. package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
  176. package/src/flow/models/blocks/form/__tests__/runJsFormSubmit.test.ts +131 -0
  177. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +435 -0
  178. package/src/flow/models/blocks/form/value-runtime/rules.ts +67 -14
  179. package/src/flow/models/blocks/form/value-runtime/runtime.ts +43 -19
  180. package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
  181. package/src/flow/models/blocks/table/JSColumnModel.tsx +10 -1
  182. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
  183. package/src/flow/models/blocks/table/TableBlockModel.tsx +29 -3
  184. package/src/flow/models/blocks/table/TableColumnModel.tsx +48 -9
  185. package/src/flow/models/blocks/table/__tests__/JSColumnModel.test.tsx +52 -5
  186. package/src/flow/models/blocks/table/__tests__/TableBlockModel.filterReset.test.ts +78 -0
  187. package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
  188. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +33 -0
  189. package/src/flow/models/blocks/table/utils.ts +7 -0
  190. package/src/flow/models/fields/AssociationFieldModel/CascadeSelectFieldModel.tsx +33 -1
  191. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +10 -1
  192. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/__tests__/popupContext.test.ts +120 -0
  193. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/actions/PopupSubTableEditActionModel.tsx +10 -2
  194. package/src/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.tsx +24 -1
  195. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +9 -136
  196. package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +167 -3
  197. package/src/flow/models/fields/AssociationFieldModel/recordSelectShared.tsx +156 -0
  198. package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
  199. package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
  200. package/src/flow/models/fields/DisplayNumberFieldModel.tsx +1 -1
  201. package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
  202. package/src/flow/models/fields/NumberFieldModel.tsx +1 -1
  203. package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
  204. package/src/flow/models/fields/__tests__/DisplayNumberFieldModel.test.ts +33 -0
  205. package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
  206. package/src/flow/models/fields/__tests__/NumberFieldModel.test.tsx +47 -0
  207. package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
  208. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +26 -10
  209. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +51 -14
  210. package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +139 -13
  211. package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
  212. package/src/flow-compat/fieldValidationConstants.ts +1 -1
  213. package/src/flow-compat/routeTypes.ts +1 -0
  214. package/src/index.ts +5 -0
  215. package/src/layout-manager/LayoutContentRoute.tsx +2 -1
  216. package/src/layout-manager/LayoutRoute.tsx +2 -1
  217. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +87 -1
  218. package/src/locale/languageCodes.ts +3 -1
  219. package/src/nocobase-buildin-plugin/index.tsx +12 -0
  220. package/src/settings-center/AdminSettingsLayout.tsx +23 -2
  221. package/src/ui-operation/index.ts +33 -0
  222. package/src/ui-operation/ui-operation-codec.ts +54 -0
  223. package/src/utils/getRouteRuntimeVersion.ts +185 -0
  224. package/src/utils/index.tsx +1 -0
@@ -45,6 +45,7 @@ interface FilterGroupProps {
45
45
  value: Record<string, any>;
46
46
  /** 自定义筛选项组件 */
47
47
  FilterItem?: React.FC<FilterItemProps>;
48
+ disabled?: boolean;
48
49
  closeIcon?: ReactNode;
49
50
  /** 是否显示边框 */
50
51
  showBorder?: boolean;
@@ -89,6 +90,7 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
89
90
  const {
90
91
  value = { logic: '$and', items: [] },
91
92
  FilterItem,
93
+ disabled = false,
92
94
  showBorder = false,
93
95
  onRemove,
94
96
  onChange,
@@ -120,11 +122,17 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
120
122
  };
121
123
 
122
124
  const handleLogicChange = (newLogic: '$and' | '$or') => {
125
+ if (disabled) {
126
+ return;
127
+ }
123
128
  value.logic = newLogic;
124
129
  onChange?.(value);
125
130
  };
126
131
 
127
132
  const handleAddCondition = () => {
133
+ if (disabled) {
134
+ return;
135
+ }
128
136
  items.push({
129
137
  path: '',
130
138
  operator: '',
@@ -134,6 +142,9 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
134
142
  };
135
143
 
136
144
  const handleAddConditionGroup = () => {
145
+ if (disabled) {
146
+ return;
147
+ }
137
148
  items.push({
138
149
  logic: '$and',
139
150
  items: [],
@@ -142,6 +153,9 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
142
153
  };
143
154
 
144
155
  const handleRemoveItem = (index: number) => {
156
+ if (disabled) {
157
+ return;
158
+ }
145
159
  items.splice(index, 1);
146
160
  onChange?.(value);
147
161
  };
@@ -160,11 +174,12 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
160
174
  <button
161
175
  type="button"
162
176
  aria-label="icon-close"
177
+ disabled={disabled}
163
178
  style={{
164
179
  position: 'absolute',
165
180
  right: 10,
166
181
  top: 10,
167
- cursor: 'pointer',
182
+ cursor: disabled ? 'not-allowed' : 'pointer',
168
183
  background: 'transparent',
169
184
  border: 0,
170
185
  padding: 0,
@@ -182,6 +197,7 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
182
197
  data-testid="filter-select-all-or-any"
183
198
  style={{ width: 'auto' }}
184
199
  value={logic}
200
+ disabled={disabled}
185
201
  onChange={handleLogicChange}
186
202
  >
187
203
  <Select.Option value="$and">{t('All')}</Select.Option>
@@ -200,6 +216,7 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
200
216
  key={getFilterItemKey(item)}
201
217
  value={item}
202
218
  FilterItem={FilterItem}
219
+ disabled={disabled}
203
220
  showBorder={true}
204
221
  onRemove={() => handleRemoveItem(index)}
205
222
  onChange={(v) => {
@@ -221,11 +238,12 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
221
238
  <button
222
239
  type="button"
223
240
  aria-label="icon-close"
241
+ disabled={disabled}
224
242
  style={{
225
243
  marginLeft: 8,
226
244
  marginRight: 8,
227
245
  flex: '0 0 auto',
228
- cursor: 'pointer',
246
+ cursor: disabled ? 'not-allowed' : 'pointer',
229
247
  background: 'transparent',
230
248
  border: 0,
231
249
  padding: 0,
@@ -251,11 +269,12 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
251
269
  <button
252
270
  type="button"
253
271
  aria-label="icon-close"
272
+ disabled={disabled}
254
273
  style={{
255
274
  marginLeft: 8,
256
275
  marginRight: 8,
257
276
  flex: '0 0 auto',
258
- cursor: 'pointer',
277
+ cursor: disabled ? 'not-allowed' : 'pointer',
259
278
  background: 'transparent',
260
279
  border: 0,
261
280
  padding: 0,
@@ -277,11 +296,12 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
277
296
  <button
278
297
  type="button"
279
298
  aria-label="icon-close"
299
+ disabled={disabled}
280
300
  style={{
281
301
  marginLeft: 8,
282
302
  marginRight: 8,
283
303
  flex: '0 0 auto',
284
- cursor: 'pointer',
304
+ cursor: disabled ? 'not-allowed' : 'pointer',
285
305
  background: 'transparent',
286
306
  border: 0,
287
307
  padding: 0,
@@ -298,7 +318,14 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
298
318
  </div>
299
319
 
300
320
  <Space size={16} style={{ marginTop: 8, marginBottom: 8 }}>
301
- <Button style={{ padding: 0 }} type="link" size="small" icon={<PlusOutlined />} onClick={handleAddCondition}>
321
+ <Button
322
+ style={{ padding: 0 }}
323
+ type="link"
324
+ size="small"
325
+ icon={<PlusOutlined />}
326
+ onClick={handleAddCondition}
327
+ disabled={disabled}
328
+ >
302
329
  {t('Add condition')}
303
330
  </Button>
304
331
  <Button
@@ -307,6 +334,7 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
307
334
  size="small"
308
335
  icon={<PlusOutlined />}
309
336
  onClick={handleAddConditionGroup}
337
+ disabled={disabled}
310
338
  >
311
339
  {t('Add condition group')}
312
340
  </Button>
@@ -34,6 +34,8 @@ const { DateFilterDynamicComponent: DateFilterDynamicComponentLazy } = lazy(
34
34
  'DateFilterDynamicComponent',
35
35
  );
36
36
 
37
+ const fallbackInputComponents = new Set(['Input', 'Input.URL', 'NanoIDInput', 'DatePicker']);
38
+
37
39
  // 简化的本地辅助:按需获取 ctx.collection 的字段树(MetaTreeNode[])
38
40
  async function buildCollectionLeftMetaTreeLocal(ctx: any): Promise<MetaTreeNode[]> {
39
41
  const resolve = async (sub: any): Promise<MetaTreeNode[]> => {
@@ -62,6 +64,51 @@ async function buildCollectionLeftMetaTreeLocal(ctx: any): Promise<MetaTreeNode[
62
64
  return await resolve(subTree);
63
65
  }
64
66
 
67
+ function buildCollectionFieldMetaNode(collection: any, path: string[]): MetaTreeNode | null {
68
+ if (!collection || !path.length) {
69
+ return null;
70
+ }
71
+
72
+ const normalizedPath = path[0] === 'collection' ? path.slice(1) : path;
73
+ if (!normalizedPath.length) {
74
+ return null;
75
+ }
76
+
77
+ const parentTitles: string[] = [];
78
+ let currentCollection = collection;
79
+ let currentNode: MetaTreeNode | null = null;
80
+
81
+ for (const segment of normalizedPath) {
82
+ const field =
83
+ currentCollection?.getField?.(segment) ??
84
+ currentCollection?.getFields?.()?.find?.((candidate: any) => candidate?.name === segment);
85
+ if (!field) {
86
+ return null;
87
+ }
88
+
89
+ const title = field.title || field.uiSchema?.title || field.name || segment;
90
+ currentNode = {
91
+ name: field.name || segment,
92
+ title,
93
+ type: field.type,
94
+ interface: field.interface,
95
+ uiSchema: field.uiSchema,
96
+ options: field.options,
97
+ paths: ['collection', ...normalizedPath.slice(0, parentTitles.length + 1)],
98
+ parentTitles: [...parentTitles],
99
+ } as MetaTreeNode;
100
+
101
+ if (field.targetCollection) {
102
+ parentTitles.push(title);
103
+ currentCollection = field.targetCollection;
104
+ } else {
105
+ currentCollection = null;
106
+ }
107
+ }
108
+
109
+ return currentNode;
110
+ }
111
+
65
112
  export interface VariableFilterItemValue {
66
113
  path: string;
67
114
  operator: string;
@@ -75,6 +122,7 @@ export interface VariableFilterItemProps {
75
122
  /** 筛选条件值对象 */
76
123
  value: VariableFilterItemValue;
77
124
  model: FlowModel;
125
+ disabled?: boolean;
78
126
  /**
79
127
  * 是否启用右侧 VariableInput(变量或静态值二合一)。
80
128
  * 默认 false:保持原有行为,右侧仅静态输入组件。
@@ -85,6 +133,10 @@ export interface VariableFilterItemProps {
85
133
  * 默认使用整棵 ctx 的 metaTree:model.context.getPropertyMetaTree()
86
134
  */
87
135
  rightMetaTree?: MetaTreeNode[] | (() => MetaTreeNode[] | Promise<MetaTreeNode[]>);
136
+ /**
137
+ * 右侧变量的领域转换器。常量和空值仍由 VariableFilterItem 处理,未匹配的值回退到 FlowEngine 默认转换器。
138
+ */
139
+ rightVariableConverters?: Pick<Converters, 'resolvePathFromValue' | 'resolveValueFromPath'>;
88
140
  ignoreFieldNames?: string[];
89
141
  maxAssociationFieldDepth?: number;
90
142
  }
@@ -226,9 +278,9 @@ function createStaticInputRenderer(
226
278
  onChange={(v: unknown) => onChange?.(v as VariableFilterItemValue['value'])}
227
279
  />
228
280
  );
229
- // 未内置的 x-component:尝试从 app 解析组件
281
+ // 未直接处理的 x-component:尝试从 app 解析组件,可由筛选器接管的过渡组件查找失败时静默回退到 Input
230
282
  if (xComp && app?.getComponent) {
231
- const Comp = app.getComponent(xComp as any);
283
+ const Comp = app.getComponent(xComp, !fallbackInputComponents.has(xComp));
232
284
  if (Comp) {
233
285
  return (
234
286
  <Comp
@@ -265,11 +317,57 @@ function normalizeRightValueInput(input: unknown) {
265
317
  return input;
266
318
  }
267
319
 
320
+ function findMetaTreeNodeByPath(metaTree: MetaTreeNode[], targetPath: string[]): MetaTreeNode | null {
321
+ if (!Array.isArray(metaTree) || !targetPath.length) {
322
+ return null;
323
+ }
324
+
325
+ const traverseByName = (nodes: MetaTreeNode[], path: string[]): MetaTreeNode | null => {
326
+ if (!path.length) {
327
+ return null;
328
+ }
329
+
330
+ const [head, ...rest] = path;
331
+ const matched = nodes.find((node) => String(node.name) === String(head));
332
+ if (!matched) {
333
+ return null;
334
+ }
335
+ if (!rest.length) {
336
+ return matched;
337
+ }
338
+ if (!Array.isArray(matched.children)) {
339
+ return null;
340
+ }
341
+ return traverseByName(matched.children as MetaTreeNode[], rest);
342
+ };
343
+
344
+ const byNames = traverseByName(metaTree, targetPath);
345
+ if (byNames) {
346
+ return byNames;
347
+ }
348
+
349
+ const topNames = new Set(metaTree.map((node) => String(node.name)));
350
+ if (!topNames.has(String(targetPath[0]))) {
351
+ return traverseByName(metaTree, targetPath.slice(1));
352
+ }
353
+
354
+ return null;
355
+ }
356
+
268
357
  /**
269
358
  * 上下文筛选项组件
270
359
  */
271
360
  export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
272
- ({ value, model, rightAsVariable, rightMetaTree, ignoreFieldNames, maxAssociationFieldDepth }) => {
361
+ ({
362
+ value,
363
+ model,
364
+ disabled = false,
365
+ rightAsVariable,
366
+ rightMetaTree,
367
+ rightVariableConverters,
368
+ ignoreFieldNames,
369
+ maxAssociationFieldDepth,
370
+ }) => {
273
371
  // 使用 View 上下文,确保可访问 ctx.view 的异步子树
274
372
  const ctx = useFlowViewContext();
275
373
  const t = model.translate;
@@ -334,6 +432,9 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
334
432
  // 处理左侧值变化(值由 converters 决定如何解析)
335
433
  const handleLeftChange = useCallback(
336
434
  (variableValue: string, meta?: MetaTreeNode) => {
435
+ if (disabled) {
436
+ return;
437
+ }
337
438
  const prevPath = value.path || '';
338
439
  const nextPath = variableValue || '';
339
440
  const changed = nextPath !== prevPath;
@@ -348,7 +449,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
348
449
  setLeftMeta(meta);
349
450
  }
350
451
  },
351
- [value],
452
+ [disabled, value],
352
453
  );
353
454
 
354
455
  // 自定义转换器来捕获 MetaTreeNode,并在选择左值时设置默认操作符
@@ -376,6 +477,9 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
376
477
  // 处理操作符变化
377
478
  const handleOperatorChange = useCallback(
378
479
  (operatorValue: string) => {
480
+ if (disabled) {
481
+ return;
482
+ }
379
483
  value.operator = operatorValue;
380
484
  const cur = operatorMetaList.find((op) => op.value === operatorValue);
381
485
  if (cur?.noValue) {
@@ -386,7 +490,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
386
490
  value.noValue = false;
387
491
  }
388
492
  },
389
- [operatorMetaList, value],
493
+ [disabled, operatorMetaList, value],
390
494
  );
391
495
 
392
496
  // 使用公共静态输入渲染器(抽取到 utils)
@@ -464,9 +568,12 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
464
568
 
465
569
  const setRightValue = useCallback(
466
570
  (next: unknown) => {
571
+ if (disabled) {
572
+ return;
573
+ }
467
574
  value.value = normalizeRightValueInput(next) as VariableFilterItemValue['value'];
468
575
  },
469
- [value],
576
+ [disabled, value],
470
577
  );
471
578
 
472
579
  // 右侧静态输入(无变量模式)与右侧 VariableInput 的静态渲染组件,统一复用
@@ -490,8 +597,14 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
490
597
 
491
598
  const renderRightValueComponent = useCallback(() => {
492
599
  // 文本类多关键词:优先使用操作符 schema 声明的组件
493
- const resolved = resolveOperatorComponent(model.context.app, operator, operatorMetaList);
494
600
  const supportKeyword = operator === '$in' || operator === '$notIn';
601
+ const operatorComponent = currentOpMeta?.schema?.['x-component'] as string | undefined;
602
+ const isLocallyRenderedComponent =
603
+ operatorComponent === 'Select' ||
604
+ (typeof operatorComponent === 'string' && fallbackInputComponents.has(operatorComponent));
605
+ const resolved = supportKeyword
606
+ ? resolveOperatorComponent(model.context.app, operator, operatorMetaList, !isLocallyRenderedComponent)
607
+ : null;
495
608
  if (resolved && supportKeyword) {
496
609
  const { Comp, props: xProps } = resolved;
497
610
  const nextProps = { ...xProps };
@@ -520,6 +633,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
520
633
  {...nextProps}
521
634
  style={{ width: '100%', ...pickStyle(nextProps.style) }}
522
635
  value={normalized}
636
+ disabled={disabled}
523
637
  onChange={(vals: any) => setRightValue(vals)}
524
638
  />
525
639
  </div>
@@ -529,10 +643,20 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
529
643
  const Comp = staticInputRenderer;
530
644
  return (
531
645
  <div style={{ flex: '1 1 40%', minWidth: 160, maxWidth: '100%' }}>
532
- <Comp value={rightValue} onChange={(val) => setRightValue(val)} />
646
+ <Comp value={rightValue} onChange={(val) => setRightValue(val)} disabled={disabled} />
533
647
  </div>
534
648
  );
535
- }, [operator, operatorMetaList, rightValue, staticInputRenderer, model.context.app, setRightValue, enumOptions]);
649
+ }, [
650
+ disabled,
651
+ operator,
652
+ operatorMetaList,
653
+ currentOpMeta,
654
+ rightValue,
655
+ staticInputRenderer,
656
+ model.context.app,
657
+ setRightValue,
658
+ enumOptions,
659
+ ]);
536
660
 
537
661
  // Null 占位组件(仿照 DefaultValue.tsx 的实现)
538
662
  const NullComponent = useMemo(() => {
@@ -582,17 +706,19 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
582
706
  const first = meta?.paths?.[0];
583
707
  if (first === 'constant') return '';
584
708
  if (first === 'null') return null;
585
- return undefined; // 交给默认逻辑格式化变量表达式
709
+ return rightVariableConverters?.resolveValueFromPath?.(meta);
586
710
  },
587
711
  resolvePathFromValue: (val) => {
588
712
  if (val === null) return ['null'];
589
- // 变量表达式:使用内置解析;其他静态值走 constant
590
- const parsed = typeof val === 'string' ? parseValueToPath(val) : undefined;
713
+ // 变量表达式:优先使用领域转换器,再回退内置解析;其他静态值走 constant
714
+ const parsed =
715
+ rightVariableConverters?.resolvePathFromValue?.(val) ??
716
+ (typeof val === 'string' ? parseValueToPath(val) : undefined);
591
717
  if (parsed) return parsed;
592
718
  return ['constant'];
593
719
  },
594
720
  };
595
- }, [NullComponent, staticInputRenderer]);
721
+ }, [NullComponent, rightVariableConverters, staticInputRenderer]);
596
722
 
597
723
  // 为 2.0 左侧字段选择器追加“接口 filterable.children”定义(如 chinaRegion 的“省市区名称”子项),
598
724
  // 以恢复 1.0 左侧子菜单的能力。
@@ -652,6 +778,34 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
652
778
  };
653
779
  }, [getFieldInterface, model, maxAssociationFieldDepth]);
654
780
 
781
+ useEffect(() => {
782
+ if (leftMeta || !value.path) {
783
+ return;
784
+ }
785
+
786
+ let cancelled = false;
787
+ const restore = async () => {
788
+ const nodes = await enhancedMetaTree();
789
+ if (cancelled) {
790
+ return;
791
+ }
792
+ const path = customConverters.resolvePathFromValue(value.path);
793
+ if (!path?.length) {
794
+ return;
795
+ }
796
+ const matched =
797
+ findMetaTreeNodeByPath(nodes, path) || buildCollectionFieldMetaNode(model.context.collection, path);
798
+ if (matched) {
799
+ setLeftMeta(matched);
800
+ }
801
+ };
802
+
803
+ restore();
804
+ return () => {
805
+ cancelled = true;
806
+ };
807
+ }, [customConverters, enhancedMetaTree, leftMeta, model.context.collection, value.path]);
808
+
655
809
  return (
656
810
  <Space wrap style={{ width: '100%' }}>
657
811
  <VariableInput
@@ -659,6 +813,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
659
813
  metaTree={enhancedMetaTree}
660
814
  onChange={handleLeftChange}
661
815
  converters={customConverters}
816
+ disabled={disabled}
662
817
  showValueComponent={false}
663
818
  style={{ flex: '1 1 40%', minWidth: 160, maxWidth: '100%' }}
664
819
  onlyLeafSelectable={true}
@@ -670,6 +825,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
670
825
  style={{ flex: '0 0 140px', minWidth: 120, maxWidth: '100%' }}
671
826
  placeholder={t('Comparison')}
672
827
  value={operator || undefined}
828
+ disabled={disabled}
673
829
  onChange={handleOperatorChange}
674
830
  >
675
831
  {operatorOptions.map((op) => (
@@ -686,6 +842,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
686
842
  onChange={(v) => setRightValue(v)}
687
843
  metaTree={mergedRightMetaTree}
688
844
  converters={rightConverters}
845
+ disabled={disabled}
689
846
  showValueComponent
690
847
  style={{ flex: '1 1 40%', minWidth: 160, maxWidth: '100%' }}
691
848
  placeholder={t('Enter value')}
@@ -140,4 +140,49 @@ describe('FilterGroup closeIcon', () => {
140
140
  expect(onChange).toHaveBeenCalledTimes(1);
141
141
  expect(value.items[0].items).toHaveLength(1);
142
142
  });
143
+
144
+ it('disables close and add actions when the filter group is disabled', () => {
145
+ const value = {
146
+ logic: '$and',
147
+ items: [
148
+ {
149
+ path: 'name',
150
+ operator: 'eq',
151
+ value: 'test',
152
+ },
153
+ ],
154
+ };
155
+ const onRemove = vi.fn();
156
+ const onChange = vi.fn();
157
+
158
+ renderWithProviders(
159
+ <FilterGroup
160
+ value={value}
161
+ FilterItem={DummyFilterItem}
162
+ showBorder
163
+ onRemove={onRemove}
164
+ onChange={onChange}
165
+ disabled
166
+ />,
167
+ );
168
+
169
+ const closeButtons = screen
170
+ .getAllByLabelText('icon-close')
171
+ .map((element) => element.closest('button'))
172
+ .filter(Boolean) as HTMLButtonElement[];
173
+ expect(closeButtons).toHaveLength(2);
174
+ expect(closeButtons[0]).toBeDisabled();
175
+ expect(closeButtons[1]).toBeDisabled();
176
+ expect(screen.getByText('Add condition').closest('button')).toBeDisabled();
177
+ expect(screen.getByText('Add condition group').closest('button')).toBeDisabled();
178
+
179
+ fireEvent.click(closeButtons[0]);
180
+ fireEvent.click(closeButtons[1]);
181
+ fireEvent.click(screen.getByText('Add condition').closest('button') as HTMLButtonElement);
182
+ fireEvent.click(screen.getByText('Add condition group').closest('button') as HTMLButtonElement);
183
+
184
+ expect(onRemove).not.toHaveBeenCalled();
185
+ expect(onChange).not.toHaveBeenCalled();
186
+ expect(value.items).toHaveLength(1);
187
+ });
143
188
  });
@@ -193,4 +193,13 @@ describe('VariableFilterItem with leftMetaTree', () => {
193
193
  fireEvent.click(screen.getByTestId('variable-input'));
194
194
  expect(value.path).toBe('title');
195
195
  });
196
+
197
+ it('restores the saved left meta so the operator select shows its label on reopen', async () => {
198
+ const value = { path: 'title', operator: '$eq', value: 'x' } as any;
199
+ const model = createModelWithCollection();
200
+
201
+ render(<VariableFilterItem value={value} model={model} rightAsVariable={false} />);
202
+
203
+ expect(await screen.findByText('Equals')).toBeInTheDocument();
204
+ });
196
205
  });