日期:2026-05-20 | 分支:master | 状态:已批准
将现有前端登录表单封装为独立的 JS Widget,嵌入方通过 <script> 标签引入后渲染登录表单,调用现有 /api/user/login 接口完成认证,登录成功后通过回调返回用户数据。
核心原则:零后端改动,纯前端实现。
<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>
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。
// 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 冲突。
直接调用现有 POST /api/user/login 接口,跨域 CORS 已开启(AllowAllOrigins + AllowCredentials)。
请求:
POST {apiUrl}/api/user/login
{ "username": "xxx", "password": "xxx" }
响应(成功):
{
"success": true,
"data": {
"id": 1,
"username": "user",
"display_name": "User",
"role": 1,
"status": 1,
"group": "default"
}
}
响应(失败):
{ "success": false, "message": "用户名或密码错误" }
web/build/static/login-widget.jsNewApiLoginWidget)由 new-api 后端的静态文件服务自动托管。构建流程:
cd web && bun run build — 构建主前端cd web/widget && bun run build — 构建 Widget(输出到 web/build/static/)web/build/static/login-widget.js 随 new-api 部署| 位置 | 改动 |
|---|---|
web/widget/ |
新增目录,Widget 源码 |
| 后端 | 无改动 |
| 构建流程 | Makefile 或 web/package.json 添加 Widget 构建步骤 |