|
|
|
@@ -0,0 +1,139 @@ |
|
|
|
# 登录 Widget 设计文档 |
|
|
|
|
|
|
|
> 日期:2026-05-20 | 分支:master | 状态:已批准 |
|
|
|
|
|
|
|
## 概述 |
|
|
|
|
|
|
|
将现有前端登录表单封装为独立的 JS Widget,嵌入方通过 `<script>` 标签引入后渲染登录表单,调用现有 `/api/user/login` 接口完成认证,登录成功后通过回调返回用户数据。 |
|
|
|
|
|
|
|
**核心原则**:零后端改动,纯前端实现。 |
|
|
|
|
|
|
|
## 嵌入方使用方式 |
|
|
|
|
|
|
|
```html |
|
|
|
<div id="login-container"></div> |
|
|
|
<script src="https://new-api.example.com/static/login-widget.js"></script> |
|
|
|
<script> |
|
|
|
const widget = NewApiLoginWidget.init({ |
|
|
|
container: '#login-container', |
|
|
|
apiUrl: 'https://new-api.example.com', |
|
|
|
onSuccess: (data) => { |
|
|
|
// data = { id, username, display_name, role, status, group } |
|
|
|
console.log('登录成功:', data); |
|
|
|
}, |
|
|
|
onError: (err) => { |
|
|
|
console.log('登录失败:', err.message); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// 销毁 |
|
|
|
widget.destroy(); |
|
|
|
</script> |
|
|
|
``` |
|
|
|
|
|
|
|
## Widget API |
|
|
|
|
|
|
|
### `NewApiLoginWidget.init(options)` |
|
|
|
|
|
|
|
| 参数 | 类型 | 必填 | 说明 | |
|
|
|
|------|------|------|------| |
|
|
|
| `container` | `string \| Element` | 是 | 挂载点,CSS 选择器或 DOM 元素 | |
|
|
|
| `apiUrl` | `string` | 是 | new-api 服务地址 | |
|
|
|
| `onSuccess` | `(data: UserData) => void` | 否 | 登录成功回调 | |
|
|
|
| `onError` | `(err: { message: string }) => void` | 否 | 登录失败回调 | |
|
|
|
| `theme` | `'light' \| 'dark'` | 否 | 主题,默认 `'light'` | |
|
|
|
|
|
|
|
### 返回值 |
|
|
|
|
|
|
|
`{ destroy: () => void }` — 调用 `destroy()` 卸载 Widget 并清理 DOM。 |
|
|
|
|
|
|
|
### 回调数据格式 |
|
|
|
|
|
|
|
```typescript |
|
|
|
// onSuccess 回调参数 |
|
|
|
interface UserData { |
|
|
|
id: number; |
|
|
|
username: string; |
|
|
|
display_name: string; |
|
|
|
role: number; |
|
|
|
status: number; |
|
|
|
group: string; |
|
|
|
} |
|
|
|
``` |
|
|
|
|
|
|
|
## 技术方案 |
|
|
|
|
|
|
|
### 构建方式 |
|
|
|
|
|
|
|
在 `web/widget/` 目录下创建独立的 Vite 入口,打包成 UMD 格式。 |
|
|
|
|
|
|
|
``` |
|
|
|
web/widget/ |
|
|
|
index.jsx # 入口:init() API,创建 Shadow DOM 并挂载 React 组件 |
|
|
|
LoginForm.jsx # 登录表单组件(参考 web/src/components/auth/LoginForm.jsx) |
|
|
|
styles.css # Widget 样式 |
|
|
|
vite.config.js # UMD 打包配置 |
|
|
|
``` |
|
|
|
|
|
|
|
### 样式隔离 |
|
|
|
|
|
|
|
使用 Shadow DOM 隔离 Widget 样式,防止与嵌入方 CSS 冲突。 |
|
|
|
|
|
|
|
### API 调用 |
|
|
|
|
|
|
|
直接调用现有 `POST /api/user/login` 接口,跨域 CORS 已开启(`AllowAllOrigins + AllowCredentials`)。 |
|
|
|
|
|
|
|
请求: |
|
|
|
```json |
|
|
|
POST {apiUrl}/api/user/login |
|
|
|
{ "username": "xxx", "password": "xxx" } |
|
|
|
``` |
|
|
|
|
|
|
|
响应(成功): |
|
|
|
```json |
|
|
|
{ |
|
|
|
"success": true, |
|
|
|
"data": { |
|
|
|
"id": 1, |
|
|
|
"username": "user", |
|
|
|
"display_name": "User", |
|
|
|
"role": 1, |
|
|
|
"status": 1, |
|
|
|
"group": "default" |
|
|
|
} |
|
|
|
} |
|
|
|
``` |
|
|
|
|
|
|
|
响应(失败): |
|
|
|
```json |
|
|
|
{ "success": false, "message": "用户名或密码错误" } |
|
|
|
``` |
|
|
|
|
|
|
|
### 打包产物 |
|
|
|
|
|
|
|
- 输出:`web/build/static/login-widget.js` |
|
|
|
- 格式:UMD(全局暴露 `NewApiLoginWidget`) |
|
|
|
- CSS 内联到 JS bundle 中,通过 Shadow DOM 注入 |
|
|
|
- React 和 ReactDOM 打包进 bundle(避免要求嵌入方提供 React) |
|
|
|
|
|
|
|
### 托管 |
|
|
|
|
|
|
|
由 new-api 后端的静态文件服务自动托管。构建流程: |
|
|
|
1. `cd web && bun run build` — 构建主前端 |
|
|
|
2. `cd web/widget && bun run build` — 构建 Widget(输出到 `web/build/static/`) |
|
|
|
3. 最终 `web/build/static/login-widget.js` 随 new-api 部署 |
|
|
|
|
|
|
|
## 改动范围 |
|
|
|
|
|
|
|
| 位置 | 改动 | |
|
|
|
|------|------| |
|
|
|
| `web/widget/` | 新增目录,Widget 源码 | |
|
|
|
| 后端 | **无改动** | |
|
|
|
| 构建流程 | `Makefile` 或 `web/package.json` 添加 Widget 构建步骤 | |
|
|
|
|
|
|
|
## 范围限定(当前迭代) |
|
|
|
|
|
|
|
- 仅支持**密码登录** |
|
|
|
- 不处理 2FA |
|
|
|
- 不处理 OAuth/Passkey 等其他登录方式 |
|
|
|
- 不管理嵌入方的认证状态(通过回调交由嵌入方处理) |