Compare commits
4 Commits
main
...
ruiling_to
Author | SHA1 | Date | |
---|---|---|---|
fa974e61f0 | |||
34f2d708dd | |||
a0981e2668 | |||
278da91d16 |
34
package.json
@ -41,45 +41,45 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"pm2": "^5.3.1",
|
||||
"pm2": "^5.4.2",
|
||||
"rimraf": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^16.3.0",
|
||||
"@commitlint/config-conventional": "^16.2.4",
|
||||
"@commitlint/prompt-cli": "^16.3.0",
|
||||
"@eslint/compat": "^1.1.1",
|
||||
"@eslint/compat": "^1.2.0",
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
"@eslint/js": "^9.10.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.2.1",
|
||||
"@eslint/js": "^9.12.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.3.1",
|
||||
"@tachybase/build": "workspace:*",
|
||||
"@tachybase/cli": "workspace:*",
|
||||
"@tachybase/preset-tachybase": "workspace:*",
|
||||
"@tachybase/test": "workspace:*",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"auto-changelog": "^2.4.0",
|
||||
"axios": "^1.6.8",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"auto-changelog": "^2.5.0",
|
||||
"axios": "^1.7.7",
|
||||
"commander": "^9.5.0",
|
||||
"eslint": "^9.10.0",
|
||||
"eslint": "^9.12.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",
|
||||
"globals": "^15.9.0",
|
||||
"husky": "^9.0.11",
|
||||
"globals": "^15.11.0",
|
||||
"husky": "^9.1.6",
|
||||
"lint-staged": "^13.3.0",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-packagejson": "^2.5.0",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-packagejson": "^2.5.3",
|
||||
"prettier-plugin-sort-json": "^4.0.0",
|
||||
"prettier-plugin-sql": "^0.17.1",
|
||||
"pretty-format": "^24.9.0",
|
||||
"pretty-quick": "^3.3.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"tsx": "^4.15.2",
|
||||
"tsx": "^4.19.1",
|
||||
"typescript": "5.4.5",
|
||||
"typescript-eslint": "^8.4.0",
|
||||
"umi": "^4.3.3",
|
||||
"typescript-eslint": "^8.9.0",
|
||||
"umi": "^4.3.27",
|
||||
"vitest": "^1.6.0"
|
||||
},
|
||||
"packageManager": "pnpm@9.12.1",
|
||||
|
2
packages/plugins/@samples/plugin-replace-page/.npmignore
Executable file
@ -0,0 +1,2 @@
|
||||
/node_modules
|
||||
/src
|
1
packages/plugins/@samples/plugin-replace-page/README.md
Executable file
@ -0,0 +1 @@
|
||||
# @samples/plugin-replace-page
|
2
packages/plugins/@samples/plugin-replace-page/client.d.ts
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
export * from './dist/client';
|
||||
export { default } from './dist/client';
|
1
packages/plugins/@samples/plugin-replace-page/client.js
Executable file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/client/index.js');
|
15
packages/plugins/@samples/plugin-replace-page/package.json
Executable 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:*"
|
||||
}
|
||||
}
|
2
packages/plugins/@samples/plugin-replace-page/server.d.ts
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
export * from './dist/server';
|
||||
export { default } from './dist/server';
|
1
packages/plugins/@samples/plugin-replace-page/server.js
Executable file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/server/index.js');
|
65
packages/plugins/@samples/plugin-replace-page/src/client/AuthLayout.tsx
Executable 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>
|
||||
);
|
||||
}
|
BIN
packages/plugins/@samples/plugin-replace-page/src/client/image.png
Executable file
After Width: | Height: | Size: 3.8 MiB |
13
packages/plugins/@samples/plugin-replace-page/src/client/index.tsx
Executable 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;
|
2
packages/plugins/@samples/plugin-replace-page/src/index.ts
Executable file
@ -0,0 +1,2 @@
|
||||
export * from './server';
|
||||
export { default } from './server';
|
1
packages/plugins/@samples/plugin-replace-page/src/server/index.ts
Executable file
@ -0,0 +1 @@
|
||||
export { default } from './plugin';
|
28
packages/plugins/@samples/plugin-replace-page/src/server/plugin.ts
Executable 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;
|
BIN
packages/plugins/@tachybase/plugin-todo-page.rar
Normal file
BIN
packages/plugins/@tachybase/plugin-todo-page/(459).png
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
packages/plugins/@tachybase/plugin-todo-page/(460).png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
packages/plugins/@tachybase/plugin-todo-page/(461).png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
packages/plugins/@tachybase/plugin-todo-page/(462).png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
packages/plugins/@tachybase/plugin-todo-page/(463).png
Normal file
After Width: | Height: | Size: 71 KiB |
2
packages/plugins/@tachybase/plugin-todo-page/.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
/node_modules
|
||||
/src
|
After Width: | Height: | Size: 89 KiB |
After Width: | Height: | Size: 82 KiB |
128
packages/plugins/@tachybase/plugin-todo-page/README.md
Normal file
@ -0,0 +1,128 @@
|
||||
## Todo 应用插件说明
|
||||
@tachybase/plugin-todo-page
|
||||
|
||||
### 简介
|
||||
该项目实现了一个 Todo 应用,用户可以通过浏览器在页面中管理待办事项。通过使用 React 进行开发,待办事项列表支持增、删、改、查操作。该插件通过 `@tachybase/client` 扩展系统功能,加载并展示 Todo 页面。
|
||||
|
||||
### 功能特性
|
||||
- **添加待办事项**:用户可以输入新的任务并将其添加到待办事项列表中。
|
||||
- **删除待办事项**:用户可以删除已完成或不再需要的任务。
|
||||
- **编辑待办事项**:用户可以对现有的任务进行修改。
|
||||
- **标记完成状态**:用户可以通过复选框标记任务是否完成。
|
||||
- **页面加载插件**:通过 `Plugin` 类将 Todo 页面的加载集成到主应用中。
|
||||
|
||||
### 环境要求
|
||||
- Node.js
|
||||
- React
|
||||
- `@tachybase/client` 插件系统支持
|
||||
|
||||
### 安装步骤
|
||||
1. **克隆项目**:
|
||||
```bash
|
||||
# git clone 项目
|
||||
git clone https://git.daoyoucloud.com/ruiling/tachybase_todo.git
|
||||
# 安装依赖(必须使用 pnpm)
|
||||
cd tachybase
|
||||
pnpm i
|
||||
# 初始化环境
|
||||
pnpm tachybase install -f
|
||||
# 然后初始化一个插件,并添加到系统中:
|
||||
pnpm pm create @samples/plugin-replace-page
|
||||
pnpm pm enable @samples/plugin-replace-page
|
||||
|
||||
pnpm pm create @tachybase/plugin-todo-page
|
||||
pnpm pm enable @tachybase/plugin-todo-page
|
||||
# 然后启动项目即可:
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
2. **安装依赖**:
|
||||
```bash
|
||||
{
|
||||
"name": "@tachybase/plugin-todo-page",
|
||||
"version": "0.21.61",
|
||||
"main": "dist/server/index.js",
|
||||
"dependencies": {
|
||||
"react-router-dom": "^6.11.2",
|
||||
"antd": "^5.18.3"
|
||||
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tachybase/client": "workspace:*",
|
||||
"@tachybase/server": "workspace:*",
|
||||
"@tachybase/plugin-auth": "workspace:*",
|
||||
"@tachybase/test": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **运行项目**:
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### API 集成说明
|
||||
该项目通过 `PluginTodoPageClient` 类集成了 Todo 页面,主要实现了 `afterAdd`、`beforeLoad` 和 `load` 方法,具体说明如下:
|
||||
|
||||
#### 1. `afterAdd()`
|
||||
该方法将在插件添加之后执行,目前预留了钩子(注释部分可实现自定义操作)。
|
||||
|
||||
#### 2. `beforeLoad()`
|
||||
在插件页面加载之前会调用此方法,允许在加载前执行相关初始化工作。
|
||||
|
||||
#### 3. `load()`
|
||||
该方法是插件的核心部分,负责将 Todo 页面的路由和组件添加到应用中:
|
||||
- `this.router.add()`:将路径 `/todo` 和 `todoList` 组件添加到路由中,确保访问该路径时显示待办事项页面。
|
||||
- `this.app.pluginSettingsManager.add()`:向应用的插件管理器中添加设置,确保在插件管理界面显示 Todo 页面的入口。
|
||||
|
||||
### 文件说明
|
||||
|
||||
#### 1. `todoList.tsx`
|
||||
- **作用**:定义了 `todoList` 组件,用于渲染待办事项页面。
|
||||
- **功能**:
|
||||
- 通过 API 接口获取待办事项列表。
|
||||
- 支持增、删、改、查、标记完成的功能。
|
||||
- 使用 React 状态管理,用户可以在页面上实时操作待办事项。
|
||||
|
||||
#### 2. `index.tsx`
|
||||
- **作用**:作为插件的入口文件,负责将 Todo 页面的路由、组件注册到系统的插件管理中。
|
||||
- **功能**:
|
||||
- 扩展系统的路由和页面展示功能。
|
||||
- 加载 `todoList` 组件并集成到主应用中。
|
||||
|
||||
### API 说明
|
||||
插件通过 HTTP API 与后端进行交互,实现对待办事项的增删改查功能:
|
||||
|
||||
- **GET `/api/todo:list`**:获取所有待办事项列表。
|
||||
- **POST `/api/todo:create`**:添加新待办事项。
|
||||
- **DELETE `/api/todo:destroy?filterByTk={id}`**:删除指定 ID 的待办事项。
|
||||
- **PUT `/api/todo:update?filterByTk={id}`**:更新指定 ID 的待办事项。
|
||||
|
||||
### 项目结构
|
||||
```
|
||||
├── src
|
||||
│ └── client
|
||||
│ ├── index.tsx # 插件入口
|
||||
│ ├── index.css # 样式文件
|
||||
│ └── todoList.tsx # 定义 TodoList 组件
|
||||
├── package.json # 项目依赖和配置信息
|
||||
└── README.md # 项目说明文档
|
||||
```
|
||||
|
||||
### 使用说明
|
||||
1. 运行项目后,访问 `/todo` 路径可以看到待办事项页面。
|
||||
![alt text]((459).png)
|
||||
1. 通过页面可以添加、删除、编辑和标记完成状态。
|
||||
新增待办事项
|
||||
![alt text]((460).png)
|
||||
![alt text](<2024-10-17 105733.png>)
|
||||
修改待办事项222为666
|
||||
![alt text]((461).png)
|
||||
修改待办事项状态为已完成
|
||||
![alt text]((462).png)
|
||||
删除待办事项
|
||||
![alt text]((463).png)
|
||||
![alt text](<2024-10-17 105821.png>)
|
||||
|
||||
### 贡献指南
|
||||
如果您发现问题或希望为此项目贡献代码,欢迎提交 PR 或 issue。
|
2
packages/plugins/@tachybase/plugin-todo-page/client.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './dist/client';
|
||||
export { default } from './dist/client';
|
1
packages/plugins/@tachybase/plugin-todo-page/client.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/client/index.js');
|
15
packages/plugins/@tachybase/plugin-todo-page/package.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "@tachybase/plugin-todo-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:*"
|
||||
}
|
||||
}
|
2
packages/plugins/@tachybase/plugin-todo-page/server.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './dist/server';
|
||||
export { default } from './dist/server';
|
1
packages/plugins/@tachybase/plugin-todo-page/server.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/server/index.js');
|
@ -0,0 +1,124 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import './index.css';
|
||||
|
||||
export function TodoLists() {
|
||||
const [todos, setTodos] = useState([]);
|
||||
const [newTodo, setNewTodo] = useState('');
|
||||
const [editingId, setEditingId] = useState(null);
|
||||
const [editingText, setEditingText] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTodos = async () => {
|
||||
const response = await fetch('http://localhost:3000/api/todo:list');
|
||||
const { data } = await response.json();
|
||||
if (Array.isArray(data)) setTodos(data);
|
||||
};
|
||||
fetchTodos();
|
||||
}, []);
|
||||
|
||||
const handleAdd = async (e) => {
|
||||
e.preventDefault();
|
||||
if (newTodo.trim()) {
|
||||
const response = await fetch('http://localhost:3000/api/todo:create', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title: newTodo, completed: false }),
|
||||
});
|
||||
const newTodoItem = await response.json();
|
||||
setTodos([...todos, newTodoItem]);
|
||||
setNewTodo('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (id) => {
|
||||
await fetch(`http://localhost:3000/api/todo:destroy?filterByTk=${id}`, { method: 'DELETE' });
|
||||
setTodos(todos.filter((todo) => todo.id !== id));
|
||||
};
|
||||
|
||||
const handleEdit = (id, title) => {
|
||||
setEditingId(id);
|
||||
setEditingText(title);
|
||||
};
|
||||
|
||||
const handleUpdate = async (id) => {
|
||||
await fetch(`http://localhost:3000/api/todo:update?filterByTk=${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title: editingText }),
|
||||
});
|
||||
setTodos(todos.map((todo) => (todo.id === id ? { ...todo, title: editingText } : todo)));
|
||||
setEditingId(null);
|
||||
setEditingText('');
|
||||
};
|
||||
|
||||
const handleToggleComplete = async (id, currentStatus) => {
|
||||
const updatedStatus = !currentStatus;
|
||||
await fetch(`http://localhost:3000/api/todo:update?filterByTk=${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ completed: updatedStatus }),
|
||||
});
|
||||
setTodos(todos.map((todo) => (todo.id === id ? { ...todo, completed: updatedStatus } : todo)));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="todoapp stack-large">
|
||||
<h1>TodoMatic</h1>
|
||||
<form onSubmit={handleAdd}>
|
||||
<h2 className="label-wrapper">
|
||||
<label htmlFor="new-todo-input" className="label__lg">
|
||||
What needs to be done?
|
||||
</label>
|
||||
</h2>
|
||||
<input
|
||||
type="text"
|
||||
id="new-todo-input"
|
||||
className="input input__lg"
|
||||
autoComplete="off"
|
||||
value={newTodo}
|
||||
onChange={(e) => setNewTodo(e.target.value)}
|
||||
/>
|
||||
<button type="submit" className="btn btn__primary btn__lg">
|
||||
Add
|
||||
</button>
|
||||
</form>
|
||||
<h2 id="list-heading">{todos.length} tasks remaining</h2>
|
||||
<ul role="list" className="todo-list stack-large stack-exception" aria-labelledby="list-heading">
|
||||
{todos.map((todo) => (
|
||||
<li key={todo.id} className="todo stack-small">
|
||||
<div className="c-cb">
|
||||
<input
|
||||
id={`todo-${todo.id}`}
|
||||
type="checkbox"
|
||||
checked={todo.completed}
|
||||
onChange={() => handleToggleComplete(todo.id, todo.completed)}
|
||||
/>
|
||||
{editingId === todo.id ? (
|
||||
<input type="text" value={editingText} onChange={(e) => setEditingText(e.target.value)} />
|
||||
) : (
|
||||
<label className="todo-label" htmlFor={`todo-${todo.id}`}>
|
||||
{todo.title}
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
<div className="btn-group">
|
||||
{editingId === todo.id ? (
|
||||
<button type="button" className="btn" onClick={() => handleUpdate(todo.id)}>
|
||||
Save
|
||||
</button>
|
||||
) : (
|
||||
<button type="button" className="btn" onClick={() => handleEdit(todo.id, todo.title)}>
|
||||
Edit
|
||||
</button>
|
||||
)}
|
||||
<button type="button" className="btn btn__danger" onClick={() => handleDelete(todo.id)}>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,296 @@
|
||||
/* RESETS */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
*:focus {
|
||||
outline: 3px dashed #228bec;
|
||||
outline-offset: 0;
|
||||
}
|
||||
html {
|
||||
font: 62.5% / 1.15 sans-serif;
|
||||
}
|
||||
h1,
|
||||
h2 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
button {
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: auto;
|
||||
overflow: visible;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
line-height: normal;
|
||||
-webkit-font-smoothing: inherit;
|
||||
-moz-osx-font-smoothing: inherit;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
button::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit;
|
||||
font-size: 100%;
|
||||
line-height: 1.15;
|
||||
margin: 0;
|
||||
}
|
||||
button,
|
||||
input {
|
||||
overflow: visible;
|
||||
}
|
||||
input[type="text"] {
|
||||
border-radius: 0;
|
||||
}
|
||||
body {
|
||||
width: 100%;
|
||||
max-width: 68rem;
|
||||
margin: 0 auto;
|
||||
font:
|
||||
1.6rem/1.25 Arial,
|
||||
sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
color: #4d4d4d;
|
||||
}
|
||||
@media screen and (min-width: 620px) {
|
||||
body {
|
||||
font-size: 1.9rem;
|
||||
line-height: 1.31579;
|
||||
}
|
||||
}
|
||||
/*END RESETS*/
|
||||
/* GLOBAL STYLES */
|
||||
.form-group > input[type="text"] {
|
||||
display: inline-block;
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
.btn {
|
||||
padding: 0.8rem 1rem 0.7rem;
|
||||
border: 0.2rem solid #4d4d4d;
|
||||
cursor: pointer;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.btn.toggle-btn {
|
||||
border-width: 1px;
|
||||
border-color: #d3d3d3;
|
||||
}
|
||||
.btn.toggle-btn[aria-pressed="true"] {
|
||||
text-decoration: underline;
|
||||
border-color: #4d4d4d;
|
||||
}
|
||||
.btn__danger {
|
||||
color: #fff;
|
||||
background-color: #ca3c3c;
|
||||
border-color: #bd2130;
|
||||
}
|
||||
.btn__filter {
|
||||
border-color: lightgrey;
|
||||
}
|
||||
.btn__primary {
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
}
|
||||
.btn-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.btn-group > * {
|
||||
flex: 1 1 49%;
|
||||
}
|
||||
.btn-group > * + * {
|
||||
margin-left: 0.8rem;
|
||||
}
|
||||
.label-wrapper {
|
||||
margin: 0;
|
||||
flex: 0 0 100%;
|
||||
text-align: center;
|
||||
}
|
||||
.visually-hidden {
|
||||
position: absolute !important;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
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;
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import { Plugin } from '@tachybase/client';
|
||||
|
||||
import { TodoLists } from './TodoList';
|
||||
|
||||
export class PluginTodoPageClient extends Plugin {
|
||||
async afterAdd() {
|
||||
// await this.app.pm.add()
|
||||
}
|
||||
|
||||
async beforeLoad() {}
|
||||
|
||||
// You can get and modify the app instance here
|
||||
async load() {
|
||||
this.router.add('todo', {
|
||||
path: '/todo',
|
||||
Component: TodoLists,
|
||||
});
|
||||
|
||||
this.app.pluginSettingsManager.add('todo', {
|
||||
title: 'Todo',
|
||||
icon: 'ApiOutlined',
|
||||
Component: TodoLists,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default PluginTodoPageClient;
|
@ -0,0 +1,2 @@
|
||||
export * from './server';
|
||||
export { default } from './server';
|
@ -0,0 +1 @@
|
||||
export { default } from './plugin';
|
@ -0,0 +1,20 @@
|
||||
import { Plugin } from '@tachybase/server';
|
||||
|
||||
export class PluginTodoPageServer extends Plugin {
|
||||
async load() {
|
||||
// 定义一个 todo 数据表,包含 title 和 completed 字段
|
||||
this.app.db.collection({
|
||||
name: 'todo',
|
||||
fields: [
|
||||
{ type: 'string', name: 'title' },
|
||||
{ type: 'boolean', name: 'completed', default: false },
|
||||
],
|
||||
});
|
||||
await this.app.db.sync();
|
||||
|
||||
// 设置 todos 数据表的访问权限为公开
|
||||
this.app.acl.allow('todo', '*', 'public');
|
||||
}
|
||||
}
|
||||
|
||||
export default PluginTodoPageServer;
|
@ -191,9 +191,8 @@ export class PresetTachyBase extends Plugin {
|
||||
}
|
||||
}
|
||||
const name = plugin[0];
|
||||
const enabled = plugin[2];
|
||||
const packageJson = await this.getPackageJson(name);
|
||||
plugins.push({ name, packageName: packageJson.name, version: packageJson.version, enabled });
|
||||
plugins.push({ name, packageName: packageJson.name, version: packageJson.version });
|
||||
}
|
||||
return plugins;
|
||||
}
|
||||
|