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
@@ -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.
|
||||||
@@ -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 + Vite,UI 组件 shadcn/ui + Tailwind CSS。
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- **图书浏览** — 搜索(书名 / 作者)、查看详情、借阅
|
||||||
|
- **个人借阅** — 查看借阅记录、归还图书、搜索借阅记录
|
||||||
|
- **管理后台** — 新增 / 修改 / 删除 / 调整库存、查看全部借阅、手动借还
|
||||||
|
- **用户认证** — JWT 登录 / 登出、角色权限控制、路由守卫
|
||||||
|
- **深色模式** — 跟随系统 / 手动切换 / localStorage 持久化
|
||||||
|
- **多平台** — Web / Linux / Windows / Android 统一代码库
|
||||||
|
- **响应式** — 桌面侧边栏 / 移动端底部标签栏自适应
|
||||||
|
|
||||||
|
## 截图
|
||||||
|
|
||||||
|
**Web 登录**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Web 首页(未登录)**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Web 普通用户**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Web 管理员**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**移动端登录**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**移动端首页(未登录)**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**移动端普通用户**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**移动端管理员**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
| 类别 | 技术 | 版本 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 框架 | 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)
|
||||||
|
|||||||
|
After Width: | Height: | Size: 320 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 260 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 286 KiB |
|
After Width: | Height: | Size: 131 KiB |
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||