# 登录 Widget 设计文档
> 日期:2026-05-20 | 分支:master | 状态:已批准
## 概述
将现有前端登录表单封装为独立的 JS Widget,嵌入方通过 `
```
## 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 等其他登录方式
- 不管理嵌入方的认证状态(通过回调交由嵌入方处理)