diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml
index 793009536..e8330d68a 100644
--- a/.github/workflows/node-ci.yml
+++ b/.github/workflows/node-ci.yml
@@ -1,6 +1,12 @@
name: Nocobase test
on:
+ push:
+ branches:
+ - main
+ - develop
+ paths-ignore:
+ - 'docs/**'
pull_request:
paths-ignore:
- 'docs/**'
diff --git a/docs/zh-CN/development/guide/ui-router.md b/docs/zh-CN/development/guide/ui-router.md
index a587e8b4f..5894440f7 100644
--- a/docs/zh-CN/development/guide/ui-router.md
+++ b/docs/zh-CN/development/guide/ui-router.md
@@ -1 +1,63 @@
# UI Router
+
+NocoBase Client 的 Router 基于 [React Router](https://v5.reactrouter.com/web/guides/quick-start),可以通过 `` 来配置 ui routes,例子如下:
+
+```tsx
+/**
+ * defaultShowCode: true
+ */
+import React from 'react';
+import { Link, MemoryRouter as Router } from 'react-router-dom';
+import { RouteRedirectProps, RouteSwitchProvider, RouteSwitch } from '@nocobase/client';
+
+const Home = () =>
Home
;
+const About = () => About
;
+
+const routes: RouteRedirectProps[] = [
+ {
+ type: 'route',
+ path: '/',
+ exact: true,
+ component: 'Home',
+ },
+ {
+ type: 'route',
+ path: '/about',
+ component: 'About',
+ },
+];
+
+export default () => {
+ return (
+
+
+ Home, About
+
+
+
+ );
+};
+```
+
+在完整的 NocoBase 应用里,可以类似以下的的方式扩展 Route:
+
+```tsx | pure
+import { RouteSwitchContext } from '@nocobase/client';
+import React, { useContext } from 'react';
+
+const HelloWorld = () => {
+ return Hello ui router
;
+};
+
+export default React.memo((props) => {
+ const ctx = useContext(RouteSwitchContext);
+ ctx.routes.push({
+ type: 'route',
+ path: '/hello-world',
+ component: HelloWorld,
+ });
+ return {props.children};
+});
+```
+
+完整示例查看 [packages/samples/custom-page](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-page)
\ No newline at end of file
diff --git a/packages/core/server/src/plugin-manager.ts b/packages/core/server/src/plugin-manager.ts
index ac8405119..4169ed6d0 100644
--- a/packages/core/server/src/plugin-manager.ts
+++ b/packages/core/server/src/plugin-manager.ts
@@ -111,11 +111,18 @@ export class PluginManager {
ctx.throw(400, 'filterByTk invalid');
}
const name = pm.getPackageName(filterByTk);
+ console.log(name, pm.plugins.keys());
const plugin = pm.get(name);
- if (plugin.model) {
+ if (plugin?.model) {
await plugin.model.destroy();
+ pm.remove(name);
+ } else {
+ await pm.repository.destroy({
+ filter: {
+ name: filterByTk,
+ },
+ });
}
- pm.remove(name);
await app.reload();
await app.start();
ctx.body = 'ok';
diff --git a/packages/samples/custom-page/README.md b/packages/samples/custom-page/README.md
new file mode 100644
index 000000000..d83f8849d
--- /dev/null
+++ b/packages/samples/custom-page/README.md
@@ -0,0 +1,28 @@
+# Custom page
+
+## Register
+
+```ts
+yarn pm add sample-custom-page
+```
+
+## Activate
+
+```bash
+yarn pm enable sample-custom-page
+```
+
+## Launch the app
+
+```bash
+# for development
+yarn dev
+
+# for production
+yarn build
+yarn start
+```
+
+## Visit the custom page
+
+Open http://localhost:13000/hello-world in a web browser.
diff --git a/packages/samples/custom-page/client.d.ts b/packages/samples/custom-page/client.d.ts
new file mode 100755
index 000000000..765db9222
--- /dev/null
+++ b/packages/samples/custom-page/client.d.ts
@@ -0,0 +1,4 @@
+// @ts-nocheck
+export * from './lib/client';
+export { default } from './lib/client';
+
diff --git a/packages/samples/custom-page/client.js b/packages/samples/custom-page/client.js
new file mode 100755
index 000000000..238820257
--- /dev/null
+++ b/packages/samples/custom-page/client.js
@@ -0,0 +1,30 @@
+"use strict";
+
+function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
+
+function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
+
+var _index = _interopRequireWildcard(require("./lib/client"));
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+var _exportNames = {};
+Object.defineProperty(exports, "default", {
+ enumerable: true,
+ get: function get() {
+ return _index.default;
+ }
+});
+
+Object.keys(_index).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
+ if (key in exports && exports[key] === _index[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function get() {
+ return _index[key];
+ }
+ });
+});
diff --git a/packages/samples/custom-page/package.json b/packages/samples/custom-page/package.json
new file mode 100644
index 000000000..61b5f8266
--- /dev/null
+++ b/packages/samples/custom-page/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "@nocobase/plugin-sample-custom-page",
+ "version": "0.7.4-alpha.7",
+ "main": "lib/server/index.js",
+ "dependencies": {},
+ "devDependencies": {
+ "@nocobase/client": "0.7.4-alpha.7",
+ "@nocobase/server": "0.7.4-alpha.7",
+ "@nocobase/test": "0.7.4-alpha.7"
+ }
+}
diff --git a/packages/samples/custom-page/server.d.ts b/packages/samples/custom-page/server.d.ts
new file mode 100755
index 000000000..e70edb928
--- /dev/null
+++ b/packages/samples/custom-page/server.d.ts
@@ -0,0 +1,4 @@
+// @ts-nocheck
+export * from './lib/server';
+export { default } from './lib/server';
+
diff --git a/packages/samples/custom-page/server.js b/packages/samples/custom-page/server.js
new file mode 100755
index 000000000..d06a7eb92
--- /dev/null
+++ b/packages/samples/custom-page/server.js
@@ -0,0 +1,30 @@
+"use strict";
+
+function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
+
+function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
+
+var _index = _interopRequireWildcard(require("./lib/server"));
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+var _exportNames = {};
+Object.defineProperty(exports, "default", {
+ enumerable: true,
+ get: function get() {
+ return _index.default;
+ }
+});
+
+Object.keys(_index).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
+ if (key in exports && exports[key] === _index[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function get() {
+ return _index[key];
+ }
+ });
+});
diff --git a/packages/samples/custom-page/src/client/index.tsx b/packages/samples/custom-page/src/client/index.tsx
new file mode 100644
index 000000000..289a2fcbd
--- /dev/null
+++ b/packages/samples/custom-page/src/client/index.tsx
@@ -0,0 +1,16 @@
+import { RouteSwitchContext } from '@nocobase/client';
+import React, { useContext } from 'react';
+
+const HelloWorld = () => {
+ return Hello ui router
;
+};
+
+export default React.memo((props) => {
+ const ctx = useContext(RouteSwitchContext);
+ ctx.routes.push({
+ type: 'route',
+ path: '/hello-world',
+ component: HelloWorld,
+ });
+ return {props.children};
+});
diff --git a/packages/samples/custom-page/src/index.ts b/packages/samples/custom-page/src/index.ts
new file mode 100644
index 000000000..7ddad5814
--- /dev/null
+++ b/packages/samples/custom-page/src/index.ts
@@ -0,0 +1 @@
+export { default } from './server';
diff --git a/packages/samples/custom-page/src/server/index.ts b/packages/samples/custom-page/src/server/index.ts
new file mode 100644
index 000000000..66d77e12b
--- /dev/null
+++ b/packages/samples/custom-page/src/server/index.ts
@@ -0,0 +1,21 @@
+import { InstallOptions, Plugin } from '@nocobase/server';
+
+export class CustomPagePlugin extends Plugin {
+ getName(): string {
+ return this.getPackageName(__dirname);
+ }
+
+ initialize() {}
+
+ beforeLoad() {
+ // TODO
+ }
+
+ async load() {}
+
+ async install(options: InstallOptions) {
+ // TODO
+ }
+}
+
+export default CustomPagePlugin;