feat(auth): add theme toggle to login page and reposition in admin layout

- Add ThemeToggle and back navigation link to LoginPage
- Move ThemeToggle from sidebar to main content header in AdminLayout
- Add MIT license file
- Add project screenshots for all views
This commit is contained in:
2026-05-26 15:08:10 +08:00
parent 325b3a67a6
commit 7ca11db9a8
13 changed files with 269 additions and 8 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 msksbr
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+231 -4
View File
@@ -1,7 +1,234 @@
# Tauri + React + Typescript # 图书管理系统(前端)
This template should help get you started developing with Tauri, React and Typescript in Vite. <p align="center">
<img src="https://img.shields.io/badge/version-v0.1-blue" alt="version">
<img src="https://img.shields.io/badge/React-19.2-61DAFB?logo=react" alt="React">
<img src="https://img.shields.io/badge/TypeScript-5.8-3178C6?logo=typescript" alt="TypeScript">
<img src="https://img.shields.io/badge/Vite-7.3-646CFF?logo=vite" alt="Vite">
<img src="https://img.shields.io/badge/Tauri-2.11-FFC131?logo=tauri" alt="Tauri">
<img src="https://img.shields.io/badge/Tailwind-4.3-38B2AC?logo=tailwindcss" alt="Tailwind CSS">
<img src="https://img.shields.io/badge/license-MIT-brightgreen" alt="license">
</p>
## Recommended IDE Setup <img src="app-icon.svg" width="200" height="200" alt="app icon">
- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) 图书管理系统的前端,支持 **Web 端****Tauri 桌面端/移动端**。基于 React + TypeScript + ViteUI 组件 shadcn/ui + Tailwind CSS。
## 功能
- **图书浏览** — 搜索(书名 / 作者)、查看详情、借阅
- **个人借阅** — 查看借阅记录、归还图书、搜索借阅记录
- **管理后台** — 新增 / 修改 / 删除 / 调整库存、查看全部借阅、手动借还
- **用户认证** — JWT 登录 / 登出、角色权限控制、路由守卫
- **深色模式** — 跟随系统 / 手动切换 / localStorage 持久化
- **多平台** — Web / Linux / Windows / Android 统一代码库
- **响应式** — 桌面侧边栏 / 移动端底部标签栏自适应
## 截图
**Web 登录**
![](screenshots/login-web.png)
**Web 首页(未登录)**
![](screenshots/guest-web.png)
**Web 普通用户**
![](screenshots/user-web.png)
**Web 管理员**
![](screenshots/admin-web.png)
**移动端登录**
![](screenshots/login-mobie.jpg)
**移动端首页(未登录)**
![](screenshots/guest-mobie.jpg)
**移动端普通用户**
![](screenshots/user-mobie.jpg)
**移动端管理员**
![](screenshots/admin-mobie.jpg)
## 技术栈
| 类别 | 技术 | 版本 | 说明 |
|------|------|------|------|
| 框架 | React | 19.2 | |
| 语言 | TypeScript | 5.8 | |
| 构建 | Vite | 7.3 | ESM 原生开发服务器,Rollup 生产打包 |
| CSS | Tailwind CSS | 4.3 | 原子化 CSS |
| UI 组件 | shadcn/ui + Radix | — | 无头组件,可控样式 |
| 路由 | React Router | 7.15 | 嵌套布局路由 |
| 数据请求 | TanStack Query | 5.100 | 缓存、自动 refetch |
| HTTP | Axios | 1.16 | 拦截器自动注入 JWT、错误处理 |
| 状态管理 | Zustand | 5.0 | auth / theme 全局状态 |
| 图标 | Lucide React | 1.16 | |
| 桌面端 | Tauri | 2.11 | Rust 后端,WebView 前端 |
| Toast | Sonner | 2.0 | |
## 快速开始
### 环境要求
- Node.js ≥ 22
- pnpm(通过 corepack 管理)
### 1. 安装依赖
```bash
pnpm install
```
### 2. 开发模式
```bash
# Web 版(默认端口 1420
pnpm dev
# 桌面端
pnpm tauri dev
# 桌面端(跳过前端的 tsc 检查,加快启动)
pnpm tauri dev --no-dev-server-wait
```
开发模式下 `/api/*` 请求自动代理到 `http://localhost:8080`(后端),可通过环境变量覆盖:
```bash
VITE_API_BASE=http://your-backend:8080 pnpm dev
```
### 3. 构建
```bash
# 仅前端(输出到 dist/
pnpm build
# Tauri 桌面端
pnpm tauri build
# Tauri Android
pnpm tauri android build
```
## 项目结构
```
src/
├── api/ # API 层 — axios 请求函数
│ ├── client.ts # axios 实例、拦截器、snake→camel 转换
│ ├── auth.ts # 认证接口
│ ├── books.ts # 图书接口
│ ├── borrows.ts # 借阅接口
│ └── admin/
│ ├── books.ts # 管理员图书接口
│ └── borrows.ts # 管理员借阅接口
├── components/
│ ├── ui/ # shadcn/ui 组件(Button、Table、Dialog 等)
│ ├── layout/ # 布局组件
│ │ ├── AppLayout.tsx # Web 版布局(顶栏)
│ │ ├── UserLayout.tsx # 用户端布局(侧边栏 + 底部标签)
│ │ └── AdminLayout.tsx # 管理端布局
│ ├── common/ # 通用组件
│ └── ThemeToggle.tsx # 深色模式切换
├── features/ # 页面功能模块
│ ├── auth/ # 登录页
│ ├── books/ # 书目浏览
│ ├── borrows/ # 我的借阅
│ └── admin/ # 管理后台(图书管理 + 借阅管理)
├── router/ # React Router 路由配置
├── store/ # Zustand 全局状态
│ ├── authStore.ts # JWT 认证状态
│ └── themeStore.ts # 深色模式状态
├── hooks/ # 通用 Hooks
├── lib/ # 工具函数(格式化、错误处理)
└── types/ # TypeScript 类型定义
```
## 部署
### 静态文件(tar.gz
```bash
pnpm build
cd dist && tar -czf bookmgr-client-web-v0.1.tar.gz *
```
解压到任意 Web 服务器即可。Nginx 需配置 SPA 路由 fallback
```nginx
location / {
try_files $uri /index.html;
}
```
### Docker
镜像基于 `nginx:alpine`,多阶段构建(Node 编译 + Nginx 服务),暴露 80 端口。
```bash
# 构建
./scripts/docker-build.sh
# 拉取镜像
docker pull git.msksbr.com/msksbr/bookmgr-client:v0.1
# 运行
docker run -d -p 80:80 git.msksbr.com/msksbr/bookmgr-client:v0.1
```
### Docker Compose
```yaml
services:
bookmgr-web:
image: git.msksbr.com/msksbr/bookmgr-client:latest
container_name: bookmgr-web
ports:
- "80:80"
```
## 开发指南
### 新增页面
1.`src/features/` 下新建模块目录
2. 创建页面组件和 `hooks.ts`
3.`src/router/index.tsx` 注册路由
4. 在对应布局组件中添加导航入口
### 新增 API 接口
1.`src/api/` 对应模块中添加请求函数
2.`src/types/api.ts` 中添加类型
3. 前端类型命名遵循后端 `vo` / `dto` 约定,字段自动 snake→camel 转换
### 图标生成
改图标后运行:
```bash
pnpm icon
```
自动同步 `app-icon.svg``32x32.png``public/`Web favicon)。
Tauri 图标用 `tauri icon``app-icon.png` 重新生成。
## 声明
本项目基于 **MIT** 协议开源。本软件无任何销售渠道,不收取费用。请遵守开源协议合法使用。
## 作者
- 个人主页:[msksbr.com](https://msksbr.com/)
- 项目仓库:[git.msksbr.com/msksbr/bookMgr-client](https://git.msksbr.com/msksbr/bookMgr-client)
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

+3 -1
View File
@@ -46,13 +46,15 @@ export default function AdminLayout() {
</nav> </nav>
<div className="flex items-center gap-2 border-t px-4 py-3"> <div className="flex items-center gap-2 border-t px-4 py-3">
<span className="min-w-0 flex-1 truncate text-sm text-muted-foreground">{username}</span> <span className="min-w-0 flex-1 truncate text-sm text-muted-foreground">{username}</span>
<ThemeToggle />
<Button variant="ghost" size="icon-sm" onClick={handleLogout} {...(!hideTitle && { title: "退出" })}> <Button variant="ghost" size="icon-sm" onClick={handleLogout} {...(!hideTitle && { title: "退出" })}>
<LogOutIcon className="size-4" /> <LogOutIcon className="size-4" />
</Button> </Button>
</div> </div>
</aside> </aside>
<main className="ml-56 flex-1 px-6 py-6"> <main className="ml-56 flex-1 px-6 py-6">
<div className="mb-4 flex justify-end">
<ThemeToggle />
</div>
<Outlet /> <Outlet />
</main> </main>
</div> </div>
+14 -3
View File
@@ -1,7 +1,8 @@
import { useState, useEffect, type FormEvent } from "react" import { useState, useEffect, type FormEvent } from "react"
import { useNavigate, Navigate } from "react-router-dom" import { useNavigate, Navigate, Link } from "react-router-dom"
import { toast } from "sonner" import { toast } from "sonner"
import { Loader2Icon } from "lucide-react" import { Loader2Icon, ArrowLeftIcon } from "lucide-react"
import ThemeToggle from "@/components/ThemeToggle"
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from "@/components/ui/card" import { Card, CardHeader, CardTitle, CardContent, CardFooter } from "@/components/ui/card"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
@@ -59,7 +60,17 @@ export default function LoginPage() {
} }
return ( return (
<div className="flex min-h-screen items-center justify-center px-4"> <div className="flex min-h-screen items-center justify-center px-4 relative">
<div className="absolute top-4 left-4">
<Button variant="ghost" size="icon-sm" asChild>
<Link to="/books">
<ArrowLeftIcon className="size-4" />
</Link>
</Button>
</div>
<div className="absolute top-4 right-4">
<ThemeToggle />
</div>
<Card className="w-full max-w-md mx-4 sm:mx-auto"> <Card className="w-full max-w-md mx-4 sm:mx-auto">
<CardHeader> <CardHeader>
<CardTitle className="text-center text-xl"></CardTitle> <CardTitle className="text-center text-xl"></CardTitle>