feat(samples): add sample plugin

This commit is contained in:
ruiling 2024-10-17 19:49:55 +08:00
parent a0981e2668
commit 34f2d708dd
23 changed files with 3038 additions and 1096 deletions

View File

@ -41,45 +41,45 @@
] ]
}, },
"dependencies": { "dependencies": {
"pm2": "^5.3.1", "pm2": "^5.4.2",
"rimraf": "^3.0.2" "rimraf": "^3.0.2"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^16.3.0", "@commitlint/cli": "^16.3.0",
"@commitlint/config-conventional": "^16.2.4", "@commitlint/config-conventional": "^16.2.4",
"@commitlint/prompt-cli": "^16.3.0", "@commitlint/prompt-cli": "^16.3.0",
"@eslint/compat": "^1.1.1", "@eslint/compat": "^1.2.0",
"@eslint/eslintrc": "^3.1.0", "@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.10.0", "@eslint/js": "^9.12.0",
"@ianvs/prettier-plugin-sort-imports": "^4.2.1", "@ianvs/prettier-plugin-sort-imports": "^4.3.1",
"@tachybase/build": "workspace:*", "@tachybase/build": "workspace:*",
"@tachybase/cli": "workspace:*", "@tachybase/cli": "workspace:*",
"@tachybase/preset-tachybase": "workspace:*", "@tachybase/preset-tachybase": "workspace:*",
"@tachybase/test": "workspace:*", "@tachybase/test": "workspace:*",
"@types/react": "^18.3.3", "@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.1",
"auto-changelog": "^2.4.0", "auto-changelog": "^2.5.0",
"axios": "^1.6.8", "axios": "^1.7.7",
"commander": "^9.5.0", "commander": "^9.5.0",
"eslint": "^9.10.0", "eslint": "^9.12.0",
"eslint-plugin-jest-dom": "^5.4.0", "eslint-plugin-jest-dom": "^5.4.0",
"eslint-plugin-react": "^7.33.0", "eslint-plugin-react": "^7.37.1",
"eslint-plugin-testing-library": "^5.11.1", "eslint-plugin-testing-library": "^5.11.1",
"globals": "^15.9.0", "globals": "^15.11.0",
"husky": "^9.0.11", "husky": "^9.1.6",
"lint-staged": "^13.3.0", "lint-staged": "^13.3.0",
"prettier": "^3.2.5", "prettier": "^3.3.3",
"prettier-plugin-packagejson": "^2.5.0", "prettier-plugin-packagejson": "^2.5.3",
"prettier-plugin-sort-json": "^4.0.0", "prettier-plugin-sort-json": "^4.0.0",
"prettier-plugin-sql": "^0.17.1", "prettier-plugin-sql": "^0.17.1",
"pretty-format": "^24.9.0", "pretty-format": "^24.9.0",
"pretty-quick": "^3.3.1", "pretty-quick": "^3.3.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"tsx": "^4.15.2", "tsx": "^4.19.1",
"typescript": "5.4.5", "typescript": "5.4.5",
"typescript-eslint": "^8.4.0", "typescript-eslint": "^8.9.0",
"umi": "^4.3.3", "umi": "^4.3.27",
"vitest": "^1.6.0" "vitest": "^1.6.0"
}, },
"packageManager": "pnpm@9.12.1", "packageManager": "pnpm@9.12.1",

View File

@ -0,0 +1,2 @@
/node_modules
/src

View File

@ -0,0 +1 @@
# @samples/plugin-replace-page

View File

@ -0,0 +1,2 @@
export * from './dist/client';
export { default } from './dist/client';

View File

@ -0,0 +1 @@
module.exports = require('./dist/client/index.js');

View File

@ -0,0 +1,15 @@
{
"name": "@samples/plugin-replace-page",
"version": "0.21.61",
"main": "dist/server/index.js",
"dependencies": {
"antd": "^5.18.3",
"react-router-dom": "^6.11.2"
},
"peerDependencies": {
"@tachybase/client": "workspace:*",
"@tachybase/plugin-auth": "workspace:*",
"@tachybase/server": "workspace:*",
"@tachybase/test": "workspace:*"
}
}

View File

@ -0,0 +1,2 @@
export * from './dist/server';
export { default } from './dist/server';

View File

@ -0,0 +1 @@
module.exports = require('./dist/server/index.js');

View File

@ -0,0 +1,65 @@
import React from 'react';
import { css, PoweredBy, useAPIClient, useRequest, useSystemSettings } from '@tachybase/client';
import { AuthenticatorsContext } from '@tachybase/plugin-auth/client';
import { Col, Row } from 'antd';
import { Outlet } from 'react-router-dom';
import authImg from './image.png';
export function CustomAuthLayout() {
const { data } = useSystemSettings();
const api = useAPIClient();
const { data: authenticators = [], error } = useRequest(() =>
api
.resource('authenticators')
.publicList()
.then((res) => {
return res?.data?.data || [];
}),
);
return (
<Row style={{ height: '100%' }}>
<Col xs={{ span: 0 }} md={{ span: 12 }}>
<img
src={authImg}
style={{
objectFit: 'cover',
objectPosition: 'center',
width: '100%',
height: '100%',
maxWidth: '100%',
display: 'block',
verticalAlign: 'middle',
}}
/>
</Col>
<Col xs={{ span: 24 }} md={{ span: 12 }}>
<div
style={{
maxWidth: 320,
margin: '0 auto',
paddingTop: '20vh',
}}
>
<h1>{data?.data?.title}</h1>
<AuthenticatorsContext.Provider value={authenticators as any}>
<Outlet />
</AuthenticatorsContext.Provider>
<div
className={css`
position: absolute;
bottom: 24px;
width: 100%;
left: 0;
text-align: center;
`}
>
<PoweredBy />
</div>
</div>
</Col>
</Row>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

View File

@ -0,0 +1,13 @@
import { Plugin } from '@tachybase/client';
import { CustomAuthLayout } from './AuthLayout';
export class PluginReplacePageClient extends Plugin {
async load() {
this.app.router.add('auth', {
Component: CustomAuthLayout,
});
}
}
export default PluginReplacePageClient;

View File

@ -0,0 +1,2 @@
export * from './server';
export { default } from './server';

View File

@ -0,0 +1 @@
export { default } from './plugin';

View File

@ -0,0 +1,28 @@
import { Plugin } from '@tachybase/server';
export class PluginReplacePageServer extends Plugin {
async afterAdd() {}
async beforeLoad() {}
async load() {
this.app.db.collection({
name: 'posts',
// 字段类型参考 packages/core/database/src/fields常见的有 string、text、integer 等
fields: [{ type: 'string', name: 'title' }],
});
await this.app.db.sync();
// 方便做测试,权限设置为公开
this.app.acl.allow('posts', '*', 'public');
}
async install() {}
async afterEnable() {}
async afterDisable() {}
async remove() {}
}
export default PluginReplacePageServer;

Binary file not shown.

View File

@ -20,7 +20,7 @@
1. **克隆项目** 1. **克隆项目**
```bash ```bash
# git clone 项目 # git clone 项目
git clone https://git.daoyoucloud.com:8443/daoyoucloud/tachybase.git git clone https://git.daoyoucloud.com/ruiling/tachybase_todo.git
# 安装依赖(必须使用 pnpm # 安装依赖(必须使用 pnpm
cd tachybase cd tachybase
pnpm i pnpm i
@ -77,14 +77,14 @@ pnpm dev
### 文件说明 ### 文件说明
#### 1. `todoList.js` #### 1. `todoList.tsx`
- **作用**:定义了 `todoList` 组件,用于渲染待办事项页面。 - **作用**:定义了 `todoList` 组件,用于渲染待办事项页面。
- **功能** - **功能**
- 通过 API 接口获取待办事项列表。 - 通过 API 接口获取待办事项列表。
- 支持增、删、改、查、标记完成的功能。 - 支持增、删、改、查、标记完成的功能。
- 使用 React 状态管理,用户可以在页面上实时操作待办事项。 - 使用 React 状态管理,用户可以在页面上实时操作待办事项。
#### 2. `PluginTodoPageClient.js` #### 2. `index.tsx`
- **作用**:作为插件的入口文件,负责将 Todo 页面的路由、组件注册到系统的插件管理中。 - **作用**:作为插件的入口文件,负责将 Todo 页面的路由、组件注册到系统的插件管理中。
- **功能** - **功能**
- 扩展系统的路由和页面展示功能。 - 扩展系统的路由和页面展示功能。

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import './index.css'; import './index.css';
export function TodoList() { export function TodoLists() {
const [todos, setTodos] = useState([]); const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState(''); const [newTodo, setNewTodo] = useState('');
const [editingId, setEditingId] = useState(null); const [editingId, setEditingId] = useState(null);

View File

@ -1,29 +1,24 @@
/* RESETS */ /* RESETS */
* *,
*::before, *::before,
*::after { *::after {
box-sizing: border-box; box-sizing: border-box;
} }
*:focus { *:focus {
outline: 3px dashed #228bec; outline: 3px dashed #228bec;
outline-offset: 0; outline-offset: 0;
} }
html { html {
font: 62.5% / 1.15 sans-serif; font: 62.5% / 1.15 sans-serif;
} }
h1, h1,
h2 { h2 {
margin-bottom: 0; margin-bottom: 0;
} }
ul { ul {
list-style: none; list-style: none;
padding: 0; padding: 0;
} }
button { button {
border: none; border: none;
margin: 0; margin: 0;
@ -38,11 +33,9 @@ button {
-moz-osx-font-smoothing: inherit; -moz-osx-font-smoothing: inherit;
-webkit-appearance: none; -webkit-appearance: none;
} }
button::-moz-focus-inner { button::-moz-focus-inner {
border: 0; border: 0;
} }
button, button,
input, input,
optgroup, optgroup,
@ -53,16 +46,13 @@ textarea {
line-height: 1.15; line-height: 1.15;
margin: 0; margin: 0;
} }
button, button,
input { input {
overflow: visible; overflow: visible;
} }
input[type="text"] { input[type="text"] {
border-radius: 0; border-radius: 0;
} }
body { body {
width: 100%; width: 100%;
max-width: 68rem; max-width: 68rem;
@ -73,74 +63,234 @@ body {
background-color: #f5f5f5; background-color: #f5f5f5;
color: #4d4d4d; color: #4d4d4d;
} }
@media screen and (min-width: 620px) { @media screen and (min-width: 620px) {
body { body {
font-size: 1.9rem; font-size: 1.9rem;
line-height: 1.31579; line-height: 1.31579;
} }
} }
/*END RESETS*/ /*END RESETS*/
/* GLOBAL STYLES */ /* GLOBAL STYLES */
.form-group>input[type="text"] { .form-group > input[type="text"] {
display: inline-block; display: inline-block;
margin-top: 0.4rem; margin-top: 0.4rem;
} }
.btn { .btn {
padding: 0.8rem 1rem 0.7rem; padding: 0.8rem 1rem 0.7rem;
border: 0.2rem solid #4d4d4d; border: 0.2rem solid #4d4d4d;
cursor: pointer; cursor: pointer;
text-transform: capitalize; text-transform: capitalize;
} }
.btn.toggle-btn { .btn.toggle-btn {
border-width: 1px; border-width: 1px;
border-color: #d3d3d3; border-color: #d3d3d3;
} }
.btn.toggle-btn[aria-pressed="true"] { .btn.toggle-btn[aria-pressed="true"] {
text-decoration: underline; text-decoration: underline;
border-color: #4d4d4d; border-color: #4d4d4d;
} }
.btn__danger { .btn__danger {
color: #fff; color: #fff;
background-color: #ca3c3c; background-color: #ca3c3c;
border-color: #bd2130; border-color: #bd2130;
} }
.btn__filter { .btn__filter {
border-color: lightgrey; border-color: lightgrey;
} }
.btn__primary { .btn__primary {
color: #fff; color: #fff;
background-color: #000; background-color: #000;
} }
.btn-group { .btn-group {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.btn-group > * {
.btn-group>* {
flex: 1 1 49%; flex: 1 1 49%;
} }
.btn-group > * + * {
.btn-group>*+* {
margin-left: 0.8rem; margin-left: 0.8rem;
} }
.label-wrapper { .label-wrapper {
margin: 0; margin: 0;
flex: 0 0 100%; flex: 0 0 100%;
text-align: center; text-align: center;
} }
.visually-hidden { .visually-hidden {
position: absolute !important; position: absolute !important;
height: 1px; height: 1px;
width: 1px; width: 1px;
overflow overflow: hidden;
clip: rect(1px 1px 1px 1px);
clip: rect(1px, 1px, 1px, 1px);
white-space: nowrap;
}
[class*="stack"] > * {
margin-top: 0;
margin-bottom: 0;
}
.stack-small > * + * {
margin-top: 1.25rem;
}
.stack-large > * + * {
margin-top: 2.5rem;
}
@media screen and (min-width: 550px) {
.stack-small > * + * {
margin-top: 1.4rem;
}
.stack-large > * + * {
margin-top: 2.8rem;
}
}
.stack-exception {
margin-top: 1.2rem;
}
/* END GLOBAL STYLES */
.todoapp {
background: #fff;
margin: 2rem 0 4rem 0;
padding: 1rem;
position: relative;
box-shadow:
0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 2.5rem 5rem 0 rgba(0, 0, 0, 0.1);
}
@media screen and (min-width: 550px) {
.todoapp {
padding: 4rem;
}
}
.todoapp > * {
max-width: 50rem;
margin-left: auto;
margin-right: auto;
}
.todoapp > form {
max-width: 100%;
}
.todoapp > h1 {
display: block;
max-width: 100%;
text-align: center;
margin: 0;
margin-bottom: 1rem;
}
.label__lg {
line-height: 1.01567;
font-weight: 300;
padding: 0.8rem;
margin-bottom: 1rem;
text-align: center;
}
.input__lg {
padding: 2rem;
border: 2px solid #000;
}
.input__lg:focus {
border-color: #4d4d4d;
box-shadow: inset 0 0 0 2px;
}
[class*="__lg"] {
display: inline-block;
width: 100%;
font-size: 1.9rem;
}
[class*="__lg"]:not(:last-child) {
margin-bottom: 1rem;
}
@media screen and (min-width: 620px) {
[class*="__lg"] {
font-size: 2.4rem;
}
}
.filters {
width: 100%;
margin: unset auto;
}
/* Todo item styles */
.todo {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.todo > * {
flex: 0 0 100%;
}
.todo-text {
width: 100%;
min-height: 4.4rem;
padding: 0.4rem 0.8rem;
border: 2px solid #565656;
}
.todo-text:focus {
box-shadow: inset 0 0 0 2px;
}
/* CHECKBOX STYLES */
.c-cb {
box-sizing: border-box;
font-family: Arial, sans-serif;
-webkit-font-smoothing: antialiased;
font-weight: 400;
font-size: 1.6rem;
line-height: 1.25;
display: block;
position: relative;
min-height: 44px;
padding-left: 40px;
clear: left;
}
.c-cb > label::before,
.c-cb > input[type="checkbox"] {
box-sizing: border-box;
top: -2px;
left: -2px;
width: 44px;
height: 44px;
}
.c-cb > input[type="checkbox"] {
-webkit-font-smoothing: antialiased;
cursor: pointer;
position: absolute;
z-index: 1;
margin: 0;
opacity: 0;
}
.c-cb > label {
font-size: inherit;
font-family: inherit;
line-height: inherit;
display: inline-block;
margin-bottom: 0;
padding: 8px 15px 5px;
cursor: pointer;
touch-action: manipulation;
}
.c-cb > label::before {
content: "";
position: absolute;
border: 2px solid currentcolor;
background: transparent;
}
.c-cb > input[type="checkbox"]:focus + label::before {
border-width: 4px;
outline: 3px dashed #228bec;
}
.c-cb > label::after {
box-sizing: content-box;
content: "";
position: absolute;
top: 11px;
left: 9px;
width: 18px;
height: 7px;
transform: rotate(-45deg);
border: solid;
border-width: 0 0 5px 5px;
border-top-color: transparent;
opacity: 0;
background: transparent;
}
.c-cb > input[type="checkbox"]:checked + label::after {
opacity: 1;
}

View File

@ -1,23 +1,25 @@
import { Plugin } from '@tachybase/client'; import { Plugin } from '@tachybase/client';
import { TodoList } from './TodoList'; import { TodoLists } from './TodoList';
export class PluginTodoPageClient extends Plugin { export class PluginTodoPageClient extends Plugin {
async afterAdd() { async afterAdd() {
// await this.app.pm.add() // await this.app.pm.add()
} }
async beforeLoad() {} // You can get and modify the app instance here async beforeLoad() {}
// You can get and modify the app instance here
async load() { async load() {
this.router.add('todo', { this.router.add('todo', {
path: '/todo', path: '/todo',
Component: TodoList, Component: TodoLists,
}); });
this.app.pluginSettingsManager.add('todo', { this.app.pluginSettingsManager.add('todo', {
title: 'todo', title: 'Todo',
icon: 'ApiOutlined', icon: 'ApiOutlined',
Component: TodoList, Component: TodoLists,
}); });
} }
} }

View File

@ -0,0 +1 @@
export { default } from './plugin';

View File

@ -1,10 +1,10 @@
import { Plugin } from '@tachybase/server'; import { Plugin } from '@tachybase/server';
export class PluginTodoServer extends Plugin { export class PluginTodoPageServer extends Plugin {
async load() { async load() {
// 定义一个 todo 数据表,包含 title 和 completed 字段 // 定义一个 todo 数据表,包含 title 和 completed 字段
this.app.db.collection({ this.app.db.collection({
name: 'todos', name: 'todo',
fields: [ fields: [
{ type: 'string', name: 'title' }, { type: 'string', name: 'title' },
{ type: 'boolean', name: 'completed', default: false }, { type: 'boolean', name: 'completed', default: false },
@ -13,8 +13,8 @@ export class PluginTodoServer extends Plugin {
await this.app.db.sync(); await this.app.db.sync();
// 设置 todos 数据表的访问权限为公开 // 设置 todos 数据表的访问权限为公开
this.app.acl.allow('todos', '*', 'public'); this.app.acl.allow('todo', '*', 'public');
} }
} }
export default PluginTodoServer; export default PluginTodoPageServer;

File diff suppressed because it is too large Load Diff