@nocobase/client-v2 3.0.0-alpha.4 → 3.0.0-alpha.5

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/client-v2",
3
- "version": "3.0.0-alpha.4",
3
+ "version": "3.0.0-alpha.5",
4
4
  "license": "Apache-2.0",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.mjs",
@@ -27,11 +27,11 @@
27
27
  "@formily/antd-v5": "1.2.3",
28
28
  "@formily/react": "^2.2.27",
29
29
  "@formily/shared": "^2.2.27",
30
- "@nocobase/evaluators": "3.0.0-alpha.4",
31
- "@nocobase/flow-engine": "3.0.0-alpha.4",
32
- "@nocobase/sdk": "3.0.0-alpha.4",
33
- "@nocobase/shared": "3.0.0-alpha.4",
34
- "@nocobase/utils": "3.0.0-alpha.4",
30
+ "@nocobase/evaluators": "3.0.0-alpha.5",
31
+ "@nocobase/flow-engine": "3.0.0-alpha.5",
32
+ "@nocobase/sdk": "3.0.0-alpha.5",
33
+ "@nocobase/shared": "3.0.0-alpha.5",
34
+ "@nocobase/utils": "3.0.0-alpha.5",
35
35
  "ahooks": "^3.7.2",
36
36
  "antd": "5.24.2",
37
37
  "antd-style": "3.7.1",
@@ -48,5 +48,5 @@
48
48
  "react-i18next": "^11.15.1",
49
49
  "react-router-dom": "^6.30.1"
50
50
  },
51
- "gitHead": "6658768567378382de758c4e814ac510d688400c"
51
+ "gitHead": "82c7ed1cdb2f247c190582e34ee37f154cac0968"
52
52
  }
@@ -347,6 +347,23 @@ describe('auth redirect helpers', () => {
347
347
  );
348
348
  });
349
349
 
350
+ it('should derive a scoped Portal signin href under the current sub-app basename', () => {
351
+ const app = {
352
+ getPublicPath: () => '/nocobase/v2/',
353
+ router: {
354
+ getBasename: () => '/nocobase/v2/apps/test-app/',
355
+ },
356
+ } satisfies Parameters<typeof buildV2SigninHref>[0];
357
+
358
+ expect(
359
+ buildV2SigninHref(app, '/nocobase/v2/apps/test-app/customer/orders', {
360
+ signInRoutePath: '/customer/signin',
361
+ }),
362
+ ).toBe(
363
+ '/nocobase/v2/apps/test-app/customer/signin?redirect=%2Fnocobase%2Fv2%2Fapps%2Ftest-app%2Fcustomer%2Forders',
364
+ );
365
+ });
366
+
350
367
  it('should redirect to sub-app signin with window.location.replace', () => {
351
368
  const replace = vi.fn();
352
369
  Object.defineProperty(globalThis.window, 'location', {
@@ -328,10 +328,14 @@ export function getCurrentV2RedirectPath(app: AppLike, locationLike: LocationLik
328
328
  *
329
329
  * @param app 当前 v2 应用实例
330
330
  * @param targetPath 登录后回跳地址
331
+ * @param options 可选的登录路由配置
331
332
  * @returns 指向 v2 登录页的 href
332
333
  */
333
- export function buildV2SigninHref(app: AppLike, targetPath: string) {
334
- return `${getV2SigninPath(app)}?redirect=${encodeURIComponent(targetPath)}`;
334
+ export function buildV2SigninHref(app: AppLike, targetPath: string, options?: { signInRoutePath?: string }) {
335
+ const signInPath = options?.signInRoutePath
336
+ ? normalizeV2RedirectPath(app, options.signInRoutePath, options.signInRoutePath)
337
+ : getV2SigninPath(app);
338
+ return `${signInPath}?redirect=${encodeURIComponent(targetPath)}`;
335
339
  }
336
340
 
337
341
  /**
@@ -339,10 +343,14 @@ export function buildV2SigninHref(app: AppLike, targetPath: string) {
339
343
  *
340
344
  * @param app 当前 v2 应用实例
341
345
  * @param targetPath 登录后回跳地址
342
- * @param options 跳转选项
346
+ * @param options 跳转和登录路由选项
343
347
  */
344
- export function redirectToV2Signin(app: AppLike, targetPath: string, options?: { replace?: boolean }) {
345
- const href = buildV2SigninHref(app, targetPath);
348
+ export function redirectToV2Signin(
349
+ app: AppLike,
350
+ targetPath: string,
351
+ options?: { replace?: boolean; signInRoutePath?: string },
352
+ ) {
353
+ const href = buildV2SigninHref(app, targetPath, options);
346
354
  if (options?.replace === false) {
347
355
  window.location.href = href;
348
356
  return;
@@ -82,6 +82,15 @@ export class CollectionBlockModel<T = DefaultStructure> extends DataBlockModel<T
82
82
  const engine = this.context.engine as FlowEngine;
83
83
  const currentVersion = this.getDirtyTrackingVersion(engine, dataSourceKey, resource, params);
84
84
 
85
+ if (resource instanceof MultiRecordResource && this.getDataLoadingMode() === 'manual' && !this.hasActiveFilters()) {
86
+ resource.setData([]);
87
+ resource.setMeta({ count: 0, hasNext: false });
88
+ resource.setPage(1);
89
+ resource.loading = false;
90
+ this.lastSeenDirtyVersion = currentVersion;
91
+ return;
92
+ }
93
+
85
94
  if (forceRefresh) {
86
95
  if (this.dirtyRefreshing) return;
87
96
  this.dirtyRefreshing = true;
@@ -128,4 +128,64 @@ describe('CollectionBlockModel initial beforeRender refresh', () => {
128
128
  expect(resource.getData()).toEqual([]);
129
129
  expect(resource.getMeta('count')).toBe(0);
130
130
  });
131
+
132
+ it('skips forced active refresh in manual mode when filters are empty', async () => {
133
+ const { model, resource } = setupModelWithManualMode();
134
+ const refreshSpy = vi.spyOn(resource, 'refresh');
135
+
136
+ resource.setData([{ id: 2, name: 'Stale' }]);
137
+ resource.setMeta({ count: 1, hasNext: true });
138
+ resource.setPage(2);
139
+ resource.loading = true;
140
+
141
+ model.onActive(true);
142
+ await Promise.resolve();
143
+ await Promise.resolve();
144
+
145
+ expect(refreshSpy).not.toHaveBeenCalled();
146
+ expect(resource.getData()).toEqual([]);
147
+ expect(resource.getMeta('count')).toBe(0);
148
+ expect(resource.getMeta('hasNext')).toBe(false);
149
+ expect(resource.getMeta('page')).toBe(1);
150
+ expect(resource.getRequestParameter('page')).toBe(1);
151
+ expect(resource.loading).toBe(false);
152
+ });
153
+
154
+ it('keeps forced active refresh in manual mode when filters are active', async () => {
155
+ const { model, resource } = setupModelWithManualMode();
156
+ const refreshSpy = vi.spyOn(resource, 'refresh').mockResolvedValue();
157
+
158
+ resource.setData([{ id: 2, name: 'Stale' }]);
159
+ resource.setMeta({ count: 1, hasNext: true, page: 2 });
160
+ model.setFilterActive('filter-form-item', true);
161
+
162
+ model.onActive(true);
163
+ await Promise.resolve();
164
+
165
+ expect(refreshSpy).toHaveBeenCalledTimes(1);
166
+ expect(resource.getData()).toEqual([{ id: 2, name: 'Stale' }]);
167
+ expect(resource.getMeta('count')).toBe(1);
168
+ });
169
+
170
+ it('skips forced active refresh in manual mode when only resource filters are active', async () => {
171
+ const { model, resource } = setupModelWithManualMode();
172
+ const refreshSpy = vi.spyOn(resource, 'refresh');
173
+
174
+ resource.setData([{ id: 2, name: 'Stale' }]);
175
+ resource.setMeta({ count: 1, hasNext: true });
176
+ resource.setPage(2);
177
+ resource.loading = true;
178
+ resource.addFilterGroup('data-scope', { status: { $eq: 'active' } });
179
+
180
+ model.onActive(true);
181
+ await Promise.resolve();
182
+
183
+ expect(refreshSpy).not.toHaveBeenCalled();
184
+ expect(resource.getData()).toEqual([]);
185
+ expect(resource.getMeta('count')).toBe(0);
186
+ expect(resource.getMeta('hasNext')).toBe(false);
187
+ expect(resource.getMeta('page')).toBe(1);
188
+ expect(resource.getRequestParameter('page')).toBe(1);
189
+ expect(resource.loading).toBe(false);
190
+ });
131
191
  });