This commit is contained in:
2026-03-14 12:32:15 +08:00
parent 3a804ac033
commit 1c4e845dae
492 changed files with 146765 additions and 0 deletions

52
.env Normal file
View File

@@ -0,0 +1,52 @@
# Django Configuration
SECRET_KEY=django-insecure-0a1bx*8!97l^4z#ml#ufn_*9ut*)zlso$*k-g^h&(2=p@^51md
DEBUG=True
#ALLOWED_HOSTS=localhost,127.0.0.1,django-host,192.168.1.22
ALLOWED_HOSTS=*
# 切换数据源支持sqlite/mysql
DB_ENGINE=sqlite
USE_REDIS_AS_DB=True
# MySQL Configuration (when USE_REDIS_AS_DB=False)
DB_NAME=hertz_server
DB_USER=root
DB_PASSWORD=123456
DB_HOST=localhost
DB_PORT=3306
# Redis Configuration
REDIS_URL=redis://127.0.0.1:6379/0
# CORS Configuration
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
CORS_ALLOW_ALL_ORIGINS=True
# Email Configuration
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
EMAIL_HOST=smtp.qq.com
EMAIL_PORT=465
EMAIL_USE_SSL=True
EMAIL_USE_TLS=False
EMAIL_HOST_USER=your_email@qq.com
EMAIL_HOST_PASSWORD=your_email_password
DEFAULT_FROM_EMAIL=your_email@qq.com
# 注册邮箱验证码开关0=关闭1=开启)
REGISTER_EMAIL_VERIFICATION=0
# Hertz Captcha Configuration
HERTZ_CAPTCHA_LENGTH=4
HERTZ_CAPTCHA_WIDTH=120
HERTZ_CAPTCHA_HEIGHT=50
HERTZ_CAPTCHA_FONT_SIZE=30
HERTZ_CAPTCHA_TIMEOUT=300
HERTZ_CAPTCHA_BACKGROUND_COLOR=#ffffff
HERTZ_CAPTCHA_FOREGROUND_COLOR=#000000
HERTZ_CAPTCHA_NOISE_LEVEL=0.3
HERTZ_CAPTCHA_REDIS_KEY_PREFIX=hertz_captcha:
# Auth Middleware Configuration - 不需要登录验证的URL模式支持正则表达式
# 格式:使用逗号分隔的正则表达式模式
# 示例:/api/demo 表示demo接口/api/.* 表示/api路径下的所有
NO_AUTH_PATTERNS=^/api/auth/login/?$,^/api/auth/register/?$,^/api/auth/email/code/?$,^/api/auth/send-email-code/?$,^/api/auth/password/reset/?$,^/api/captcha/.*$,^/api/docs/.*$,^/api/redoc/.*$,^/api/schema/.*$,^/admin/.*$,^/static/.*$,^/media/.*$,^/demo/.*$,^/websocket/.*$,^/api/system/.*$,^/yolo/.*$,

101
.gitignore vendored Normal file
View File

@@ -0,0 +1,101 @@
# Python bytecode
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# python envs
venv/
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Django
*.log
local_settings.py
media/
staticfiles/
static_root/
# IDE / editors
.idea/
.vscode/
*.iml
# Type checking
.mypy_cache/
.pytype/
.pyre/
# Celery
celerybeat-schedule
celerybeat.pid
# Sphinx docs
docs/_build/
# PyInstaller
*.manifest
*.spec
# Project-specific data/models (uploads & intermediates)
media/models/
media/uploads/
media/yolo/temp/
media/yolo/models/
media/sklearn/models/
media/detection/temp/
media/detection/result/
media/detection/original/
# Logs
logs/
*.log
# OS files
.DS_Store
Thumbs.db
# Yolo models
hertz_studio_django_utils/yolo/Train/runs/
# 技术支持文档
shared/

BIN
data/db.sqlite3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,246 @@
# Hertz Studio Django AI聊天模块接口文档
- 基础路径: `/api/ai/`
- 统一响应: 使用 `HertzResponse`,结构如下
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {}
}
```
- 路由挂载: 项目主路由通过 `path('api/ai/', include('hertz_studio_django_ai.urls'))` 挂载(`hertz_server_django/urls.py:23`)。
- 认证说明: 所有接口需在请求头携带 `Authorization: Bearer <access_token>``hertz_studio_django_ai/views.py:34` 使用 `login_required`)。
## 接口列表
### 获取聊天列表
- 方法: `GET`
- 路径: `/api/ai/chats/`
- 查询参数:
- `query` 可选,按标题模糊搜索
- `page` 可选,默认 `1`
- `page_size` 可选,默认 `10`
- 实现: `AIChatListView.get``hertz_studio_django_ai/views.py:36`
- 请求示例:
```http
GET /api/ai/chats/?query=Python&page=1&page_size=10
Authorization: Bearer <access_token>
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"total": 25,
"page": 1,
"page_size": 10,
"chats": [
{
"id": 1,
"title": "Python编程问题",
"created_at": "2024-01-15 10:30:00",
"updated_at": "2024-01-15 10:35:00",
"latest_message": "如何使用Django创建API接口..."
}
]
}
}
```
### 创建聊天
- 方法: `POST`
- 路径: `/api/ai/chats/create/`
- 请求体: `application/json`
- 字段: `title` 可选,默认 `"新对话"`
- 实现: `AIChatCreateView.post``hertz_studio_django_ai/views.py:100`
- 请求示例:
```http
POST /api/ai/chats/create/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"title": "AI编程助手"
}
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "创建成功",
"data": {
"chat_id": 3,
"title": "AI编程助手"
}
}
```
### 聊天详情
- 方法: `GET`
- 路径: `/api/ai/chats/{chat_id}/`
- 路径参数: `chat_id` 聊天ID整数
- 实现: `AIChatDetailView.get``hertz_studio_django_ai/views.py:137`
- 请求示例:
```http
GET /api/ai/chats/1/
Authorization: Bearer <access_token>
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"id": 1,
"title": "Python编程问题",
"created_at": "2024-01-15 10:30:00",
"updated_at": "2024-01-15 10:35:00",
"messages": [
{
"id": 1,
"role": "user",
"content": "如何使用Django创建API接口",
"created_at": "2024-01-15 10:30:00"
},
{
"id": 2,
"role": "assistant",
"content": "使用Django REST Framework可以快速构建API...",
"created_at": "2024-01-15 10:30:30"
}
]
}
}
```
### 更新聊天
- 方法: `PUT`
- 路径: `/api/ai/chats/{chat_id}/update/`
- 路径参数: `chat_id` 聊天ID整数
- 请求体: `application/json`
- 字段: `title` 新标题
- 实现: `AIChatUpdateView.put``hertz_studio_django_ai/views.py:192`
- 请求示例:
```http
PUT /api/ai/chats/1/update/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"title": "更新后的标题"
}
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "更新成功",
"data": null
}
```
### 删除聊天(批量)
- 方法: `POST`
- 路径: `/api/ai/chats/delete/`
- 请求体: `application/json`
- 字段: `chat_ids` 要删除的聊天ID数组整数
- 实现: `AIChatDeleteView.post``hertz_studio_django_ai/views.py:231`
- 请求示例:
```http
POST /api/ai/chats/delete/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"chat_ids": [1, 2, 3]
}
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "成功删除3个聊天",
"data": null
}
```
### 发送消息
- 方法: `POST`
- 路径: `/api/ai/chats/{chat_id}/send/`
- 路径参数: `chat_id` 聊天ID整数
- 请求体: `application/json`
- 字段: `content` 必填,消息内容,不能为空
- 实现: `AIChatSendMessageView.post``hertz_studio_django_ai/views.py:280`
- 请求示例:
```http
POST /api/ai/chats/1/send/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"content": "你好请介绍一下Python的特点"
}
```
- 成功返回示例:
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"user_message": {
"id": 5,
"role": "user",
"content": "你好请介绍一下Python的特点",
"created_at": "2024-01-15 11:30:00"
},
"ai_message": {
"id": 6,
"role": "assistant",
"content": "Python是一种高级编程语言语法简洁生态丰富...",
"created_at": "2024-01-15 11:30:05"
}
}
}
```
- 失败返回示例(参数验证失败):
```json
{
"success": false,
"code": 422,
"message": "参数验证失败",
"data": {
"content": ["消息内容不能为空"]
}
}
```
- 失败返回示例(聊天不存在):
```json
{
"success": false,
"code": 404,
"message": "聊天不存在或无权访问",
"data": null
}
```
- 失败返回示例AI生成失败:
```json
{
"success": false,
"code": 500,
"message": "AI回复生成失败服务不可用",
"data": null
}
```
## 附注
- 列表与详情时间字段为字符串格式 `YYYY-MM-DD HH:mm:ss``hertz_studio_django_ai/views.py:65`、`hertz_studio_django_ai/views.py:151`)。
- AI模型名称由配置项 `settings.AI_MODEL_NAME` 控制,默认 `deepseek-r1:1.5b``hertz_studio_django_ai/views.py:263`)。

View File

@@ -0,0 +1,367 @@
# Hertz Studio Django Wiki 接口文档
- 基础路径: `/api/wiki/`
- 统一响应: 使用 `HertzResponse`,结构如下(参考 `hertz_studio_django_utils/responses/HertzResponse.py`
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {}
}
```
- 路由挂载: 项目主路由中已通过 `path('api/wiki/', include('hertz_studio_django_wiki.urls'))` 挂载(`hertz_server_django/urls.py:29`)。
- 认证说明: 标注“需要登录”的接口需在请求头携带 `Authorization: Bearer <token>``hertz_studio_django_auth/utils/decorators/auth_decorators.py:1`)。
## 一、知识分类管理
### 1获取分类列表
- 方法: `GET`
- 路径: `/api/wiki/categories/`
- 认证: 不需要
- 查询参数:
- `page`: 页码,默认 `1`
- `page_size`: 每页数量,默认 `10`
- `name`: 分类名称关键字
- `parent_id`: 父分类ID`0` 表示顶级)
- `is_active`: `true/false`
- 实现: `wiki_category_list``hertz_studio_django_wiki/views.py:41`
- 请求示例:
```bash
curl "http://localhost:8000/api/wiki/categories/?page=1&page_size=10&name=技术"
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"list": [
{
"id": 1,
"name": "技术文档",
"description": "技术相关文档",
"parent": null,
"parent_name": null,
"sort_order": 1,
"is_active": true,
"created_at": "2024-01-01T10:00:00Z",
"updated_at": "2024-01-01T10:00:00Z",
"children_count": 3,
"articles_count": 15,
"full_path": "技术文档"
}
],
"total": 1,
"page": 1,
"page_size": 10
}
}
```
### 2获取树形分类
- 方法: `GET`
- 路径: `/api/wiki/categories/tree/`
- 认证: 不需要
- 实现: `wiki_category_tree``hertz_studio_django_wiki/views.py:101`
- 请求示例:
```bash
curl "http://localhost:8000/api/wiki/categories/tree/"
```
- 返回示例:
```json
[
{
"id": 1,
"name": "技术文档",
"description": "技术相关文档",
"sort_order": 1,
"is_active": true,
"articles_count": 15,
"children": [
{"id": 2, "name": "后端", "children": []}
]
}
]
```
### 3创建分类
- 方法: `POST`
- 路径: `/api/wiki/categories/create/`
- 认证: 不需要
- 请求体: `application/json`
- 字段: `name`, `description`, `parent`, `sort_order`, `is_active`
- 实现: `wiki_category_create``hertz_studio_django_wiki/views.py:136`
- 请求示例:
```http
POST /api/wiki/categories/create/
Content-Type: application/json
{
"name": "新分类",
"description": "分类描述",
"parent": null,
"sort_order": 10,
"is_active": true
}
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "知识分类创建成功",
"data": {
"id": 5,
"name": "新分类",
"description": "分类描述",
"parent": null,
"parent_name": null,
"sort_order": 10,
"is_active": true,
"created_at": "2025-11-17T10:00:00Z",
"updated_at": "2025-11-17T10:00:00Z",
"children_count": 0,
"articles_count": 0,
"full_path": "新分类"
}
}
```
### 4分类详情
- 方法: `GET`
- 路径: `/api/wiki/categories/{category_id}/`
- 认证: 不需要
- 实现: `wiki_category_detail``hertz_studio_django_wiki/views.py:178`
- 请求示例:
```bash
curl "http://localhost:8000/api/wiki/categories/1/"
```
- 返回示例: 同“获取分类列表”中的单项结构。
### 5更新分类
- 方法: `PUT`(支持部分更新)
- 路径: `/api/wiki/categories/{category_id}/update/`
- 认证: 不需要
- 请求体: `application/json`
- 可更新字段: `name`, `description`, `parent`, `sort_order`, `is_active`
- 实现: `wiki_category_update``hertz_studio_django_wiki/views.py:220`
- 请求示例:
```http
PUT /api/wiki/categories/1/update/
Content-Type: application/json
{
"name": "更新后的分类名",
"description": "更新后的描述",
"sort_order": 20
}
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "知识分类更新成功", "data": {"id": 1, "name": "更新后的分类名"}}
```
### 6删除分类
- 方法: `DELETE`
- 路径: `/api/wiki/categories/{category_id}/delete/`
- 认证: 不需要
- 行为: 软删除(将 `is_active=false`);若存在子分类或文章将返回错误
- 实现: `wiki_category_delete``hertz_studio_django_wiki/views.py:270`
- 请求示例:
```bash
curl -X DELETE "http://localhost:8000/api/wiki/categories/1/delete/"
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "知识分类删除成功"}
```
## 二、知识文章管理
### 1获取文章列表
- 方法: `GET`
- 路径: `/api/wiki/articles/`
- 认证: 不需要
- 查询参数:
- `page`, `page_size`
- `title`: 标题关键字
- `category_id`: 分类ID
- `author_id`: 作者ID
- `status`: `draft|published|archived`
- 实现: `wiki_article_list``hertz_studio_django_wiki/views.py:318`
- 请求示例:
```bash
curl "http://localhost:8000/api/wiki/articles/?page=1&page_size=10&category_id=1&status=published"
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"list": [
{
"id": 101,
"title": "如何部署Django",
"summary": "部署流程概览",
"image": null,
"category_name": "技术文档",
"author_name": "alice",
"status": "published",
"status_display": "已发布",
"view_count": 42,
"created_at": "2025-11-01T09:00:00Z",
"updated_at": "2025-11-10T09:00:00Z",
"published_at": "2025-11-10T09:00:00Z"
}
],
"total": 1,
"page": 1,
"page_size": 10
}
}
```
### 2创建文章需要登录
- 方法: `POST`
- 路径: `/api/wiki/articles/create/`
- 认证: 需要登录(`Authorization: Bearer <token>`
- 请求体: `application/json`
- 字段: `title`, `content`, `summary`, `image`, `category`, `status`, `tags`, `sort_order`
- 实现: `wiki_article_create``hertz_studio_django_wiki/views.py:384`
- 请求示例:
```http
POST /api/wiki/articles/create/
Authorization: Bearer <token>
Content-Type: application/json
{
"title": "新文章",
"content": "文章内容...",
"summary": "文章摘要...",
"image": null,
"category": 1,
"status": "draft",
"tags": "django,部署",
"sort_order": 10
}
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "知识文章创建成功",
"data": {
"id": 102,
"title": "新文章",
"content": "文章内容...",
"summary": "文章摘要...",
"image": null,
"category": 1,
"category_name": "技术文档",
"author": 2,
"author_name": "alice",
"status": "draft",
"status_display": "草稿",
"tags": "django,部署",
"tags_list": ["django", "部署"],
"view_count": 0,
"sort_order": 10,
"created_at": "2025-11-17T11:00:00Z",
"updated_at": "2025-11-17T11:00:00Z",
"published_at": null
}
}
```
### 3文章详情
- 方法: `GET`
- 路径: `/api/wiki/articles/{article_id}/`
- 认证: 不需要
- 行为: 获取详情并增加浏览量
- 实现: `wiki_article_detail``hertz_studio_django_wiki/views.py:426`
- 请求示例:
```bash
curl "http://localhost:8000/api/wiki/articles/102/"
```
- 返回示例: 同“创建文章”中的完整字段结构,`view_count` 将递增。
### 4更新文章
- 方法: `PUT`(支持部分更新)
- 路径: `/api/wiki/articles/{article_id}/update/`
- 认证: 不需要
- 请求体: `application/json`
- 可更新字段: `title`, `content`, `summary`, `image`, `category`, `status`, `tags`, `sort_order`
- 实现: `wiki_article_update``hertz_studio_django_wiki/views.py:472`
- 请求示例:
```http
PUT /api/wiki/articles/102/update/
Content-Type: application/json
{
"status": "published",
"summary": "更新后的摘要"
}
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "知识文章更新成功", "data": {"id": 102, "status": "published"}}
```
### 5删除文章
- 方法: `DELETE`
- 路径: `/api/wiki/articles/{article_id}/delete/`
- 认证: 不需要
- 实现: `wiki_article_delete``hertz_studio_django_wiki/views.py:518`
- 请求示例:
```bash
curl -X DELETE "http://localhost:8000/api/wiki/articles/102/delete/"
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "知识文章删除成功"}
```
### 6发布文章
- 方法: `POST`
- 路径: `/api/wiki/articles/{article_id}/publish/`
- 认证: 不需要
- 实现: `wiki_article_publish``hertz_studio_django_wiki/views.py:561`
- 请求示例:
```bash
curl -X POST "http://localhost:8000/api/wiki/articles/102/publish/"
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "知识文章发布成功"}
```
- 失败示例(已发布再次发布):
```json
{"success": false, "code": 500, "message": "系统错误", "error": "文章已经是发布状态"}
```
### 7归档文章
- 方法: `POST`
- 路径: `/api/wiki/articles/{article_id}/archive/`
- 认证: 不需要
- 实现: `wiki_article_archive``hertz_studio_django_wiki/views.py:609`
- 请求示例:
```bash
curl -X POST "http://localhost:8000/api/wiki/articles/102/archive/"
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "知识文章归档成功"}
```
## 三、备注
- 文章状态枚举: `draft|published|archived``hertz_studio_django_wiki/models.py:35`)。
- 分类软删除通过 `is_active=false` 实现;删除校验会阻止删除存在子分类或文章的分类(`views.py:270`)。
- 文章详情接口会递增 `view_count``hertz_studio_django_wiki/models.py:70` 和 `views.py:431`)。

View File

@@ -0,0 +1,461 @@
# Hertz Studio Django YOLO 接口文档
- 基础路径: `/api/yolo/`
- 统一响应: 使用 `HertzResponse` 封装,结构为:
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {}
}
```
参考 `hertz_studio_django_utils/responses/HertzResponse.py`
- 认证说明: 标注“需要登录”的接口需在请求头携带 `Authorization: Bearer <token>`,验证逻辑参考 `hertz_studio_django_auth/utils/decorators/auth_decorators.py`。
## 一、模型上传与转换
### 1上传模型压缩包或文件夹
- 方法: `POST`
- 路径: `/api/yolo/upload/`
- 认证: 不需要
- 请求类型: `multipart/form-data`
- 参数:
- `zip_file`: ZIP 压缩包文件,与 `folder_files` 互斥
- `folder_files[...]`: 文件夹内的多个文件(键名形如 `folder_files[path/to/file]`),与 `zip_file` 互斥
- `name`: 模型名称(必填)
- `version`: 模型版本(默认 `1.0`
- `description`: 模型描述(可选)
- 参考实现: `views.py` 中 `model_upload``_handle_zip_upload``_handle_folder_upload``_validate_and_create_model`d:\All_template\yolo\hertz_studio_django_yolo\views.py:1018,1058,1129,1182
- 示例请求ZIP 上传):
```bash
curl -X POST "http://localhost:8000/api/yolo/upload/" \
-F "zip_file=@/path/to/yolo_model.zip" \
-F "name=MyYolo" \
-F "version=1.0" \
-F "description=Demo model"
```
- 示例响应:
```json
{
"success": true,
"code": 200,
"message": "模型上传成功",
"data": {
"id": 12,
"name": "MyYolo",
"version": "1.0",
"folder_path": "/absolute/path/to/media/models/MyYolo_xxxxxxxx",
"weights_path": "/absolute/path/to/media/models/MyYolo_xxxxxxxx/weights",
"model_path": "/absolute/path/to/media/models/MyYolo_xxxxxxxx/weights/best.pt",
"best_model_path": "/absolute/path/to/media/models/.../weights/best.pt",
"last_model_path": "/absolute/path/to/media/models/.../weights/last.pt",
"categories": {"0":"person","1":"bicycle"},
"created_at": "2025-10-22T03:20:00Z"
}
}
```
### 2上传 .pt 并转换为 ONNX
- 方法: `POST`
- 路径: `/api/yolo/onnx/upload/`
- 认证: 不需要
- 请求类型: `multipart/form-data`
- 参数:
- `file`: `.pt` 模型文件(必填)
- `imgsz`: 导出图像尺寸(如 `640` 或 `640,640`,默认 `640`
- `opset`: ONNX opset 版本(默认 `12`
- `simplify`: 是否简化 ONNX`true/false`,默认 `false`
- 参考实现: `views.py` 中 `upload_pt_convert_onnx`d:\All_template\yolo\hertz_studio_django_yolo\views.py:877
- 示例请求:
```bash
curl -X POST "http://localhost:8000/api/yolo/onnx/upload/" \
-F "file=@/path/to/best.pt" \
-F "imgsz=640" \
-F "opset=12" \
-F "simplify=true"
```
- 示例响应:
```json
{
"success": true,
"code": 200,
"message": "ONNX 导出成功",
"data": {
"onnx_relative_path": "yolo/ONNX/best_xxxxxxxx.onnx",
"download_url": "http://localhost:8000/media/yolo/ONNX/best_xxxxxxxx.onnx",
"labels_relative_path": "yolo/ONNX/best_xxxxxxxx.labels.json",
"labels_download_url": "http://localhost:8000/media/yolo/ONNX/best_xxxxxxxx.labels.json"
}
}
```
## 二、模型管理
### 1获取模型列表
- 方法: `GET`
- 路径: `/api/yolo/models/`
- 认证: 不需要
- 参考实现: `model_list`d:\All_template\yolo\hertz_studio_django_yolo\views.py:101
- 示例响应:
```json
{
"success": true,
"code": 200,
"message": "获取模型列表成功",
"data": [
{"id": 1, "name": "ModelA", "version": "1.0", "is_enabled": true, "created_at": "2025-10-22T03:20:00Z"}
]
}
```
### 2获取模型详情
- 方法: `GET`
- 路径: `/api/yolo/models/{pk}/`
- 认证: 不需要
- 参考实现: `model_detail`d:\All_template\yolo\hertz_studio_django_yolo\views.py:121
- 示例响应(节选):
```json
{
"success": true,
"code": 200,
"message": "获取模型详情成功",
"data": {
"id": 1,
"name": "ModelA",
"version": "1.0",
"model_file": "/media/yolo/models/modela.pt",
"model_folder_path": "/abs/path/models/modela_...",
"model_path": "/abs/path/models/modela_/weights/best.pt",
"weights_folder_path": "/abs/path/models/modela_/weights",
"categories": {"0": "person"},
"is_enabled": true,
"description": "...",
"created_at": "...",
"updated_at": "..."
}
}
```
### 2更新模型
- 方法: `PUT` 或 `PATCH`
- 路径: `/api/yolo/models/{pk}/update/`
- 认证: 不需要
- 请求类型: `application/json` 或 `multipart/form-data`
- 可更新字段: `description`, `is_enabled`,(如上传 `model_file` 必须为 `.pt`
- 参考实现: `model_update`d:\All_template\yolo\hertz_studio_django_yolo\views.py:134
- 示例请求PATCH:
```http
PATCH /api/yolo/models/1/update/
Content-Type: application/json
{
"description": "更新描述",
"is_enabled": true
}
```
- 示例响应:
```json
{"success": true, "code": 200, "message": "模型更新成功", "data": {"id": 1, "name": "ModelA", "is_enabled": true}}
```
### 3删除模型
- 方法: `DELETE`
- 路径: `/api/yolo/models/{pk}/delete/`
- 认证: 不需要
- 参考实现: `model_delete`d:\All_template\yolo\hertz_studio_django_yolo\views.py:152
- 示例响应:
```json
{"success": true, "code": 200, "message": "模型删除成功"}
```
### 4启用指定模型
- 方法: `POST`
- 路径: `/api/yolo/models/{pk}/enable/`
- 认证: 不需要
- 行为: 先禁用其他模型,再启用当前模型
- 参考实现: `model_enable`d:\All_template\yolo\hertz_studio_django_yolo\views.py:165
- 示例响应:
```json
{"success": true, "code": 200, "message": "模型 ModelA 已启用", "data": {"id": 1, "is_enabled": true}}
```
### 5获取当前启用的模型
- 方法: `GET`
- 路径: `/api/yolo/models/enabled/`
- 认证: 不需要
- 参考实现: `model_enabled`d:\All_template\yolo\hertz_studio_django_yolo\views.py:186
- 示例响应:
```json
{"success": true, "code": 200, "message": "获取启用模型成功", "data": {"id": 1, "name": "ModelA"}}
```
### 6创建模型占位
- 方法: `POST`
- 路径: `/api/yolo/models/create/`
- 说明: 返回 405提示使用 `/api/yolo/upload/`
- 参考实现: `model_create`d:\All_template\yolo\hertz_studio_django_yolo\views.py:114
## 三、模型类别管理
> 提示:类别通常随模型上传自动导入。手动创建/删除不推荐,仅保留接口。
### 1获取类别列表
- 方法: `GET`
- 路径: `/api/yolo/categories/`
- 认证: 不需要
- 参考实现: `category_list`d:\All_template\yolo\hertz_studio_django_yolo\views.py:1301
### 2获取类别详情
- 方法: `GET`
- 路径: `/api/yolo/categories/{pk}/`
- 认证: 不需要
- 参考实现: `category_detail`d:\All_template\yolo\hertz_studio_django_yolo\views.py:1329
### 3更新类别
- 方法: `PUT` 或 `PATCH`
- 路径: `/api/yolo/categories/{pk}/update/`
- 认证: 不需要
- 请求类型: `application/json`
- 可更新字段: `alias`, `alert_level``high|medium|low|none`, `is_active`
- 参考实现: `category_update`d:\All_template\yolo\hertz_studio_django_yolo\views.py:1342
- 示例请求PATCH:
```http
PATCH /api/yolo/categories/10/update/
Content-Type: application/json
{"alias": "行人", "alert_level": "high", "is_active": true}
```
- 示例响应:
```json
{"success": true, "code": 200, "message": "更新类别成功", "data": {"id": 10, "alias": "行人", "alert_level": "high"}}
```
### 4切换类别启用状态
- 方法: `POST`
- 路径: `/api/yolo/categories/{pk}/toggle-status/`
- 认证: 不需要
- 参考实现: `category_toggle_status`d:\All_template\yolo\hertz_studio_django_yolo\views.py:1385
- 示例响应:
```json
{"success": true, "code": 200, "message": "类别 'person' 启用成功", "data": {"is_active": true}}
```
### 5获取启用的类别列表
- 方法: `GET`
- 路径: `/api/yolo/categories/active/`
- 认证: 不需要
- 参考实现: `category_active_list`d:\All_template\yolo\hertz_studio_django_yolo\views.py:1405
### 6创建类别不推荐
- 方法: `POST`
- 路径: `/api/yolo/categories/create/`
- 认证: 不需要
- 请求类型: `application/json`
- 参考实现: `category_create`d:\All_template\yolo\hertz_studio_django_yolo\views.py:1312
### 7删除类别不推荐
- 方法: `DELETE`
- 路径: `/api/yolo/categories/{pk}/delete/`
- 认证: 不需要
- 参考实现: `category_delete`d:\All_template\yolo\hertz_studio_django_yolo\views.py:1362
## 四、目标检测
### 执行检测
- 方法: `POST`
- 路径: `/api/yolo/detect/`
- 认证: 需要登录(`Authorization: Bearer <token>`
- 请求类型: `multipart/form-data`
- 参数:
- `file`: 要检测的图片或视频文件(支持图片:`.jpg,.jpeg,.png,.bmp,.tiff,.webp`;视频:`.mp4,.avi,.mov,.mkv,.wmv,.flv`
- `model_id`: 指定模型ID可选未提供则使用当前启用模型
- `confidence_threshold`: 置信度阈值(默认 `0.5`,范围 `0.1-1.0`
- 参考实现: `yolo_detection`d:\All_template\yolo\hertz_studio_django_yolo\views.py:446
- 示例请求:
```bash
curl -X POST "http://localhost:8000/api/yolo/detect/" \
-H "Authorization: Bearer <token>" \
-F "file=@/path/to/image.jpg" \
-F "model_id=1" \
-F "confidence_threshold=0.5"
```
- 示例响应:
```json
{
"success": true,
"code": 200,
"message": "检测完成",
"data": {
"detection_id": 1001,
"result_file_url": "/media/detection/result/result_xxx.jpg",
"original_file_url": "/media/detection/original/uuid_image.jpg",
"object_count": 3,
"detected_categories": ["person"],
"confidence_scores": [0.91, 0.87, 0.79],
"avg_confidence": 0.8567,
"processing_time": 0.43,
"model_used": "ModelA 1.0",
"confidence_threshold": 0.5,
"user_id": 2,
"user_name": "alice",
"alert_level": "medium"
}
}
```
## 五、检测记录
### 1获取检测记录列表
- 方法: `GET`
- 路径: `/api/yolo/detections/`
- 认证: 不需要
- 查询参数:
- `type`: `image` 或 `video`
- `model_id`: 模型ID
- `user_id`: 用户ID
- 参考实现: `detection_list`d:\All_template\yolo\hertz_studio_django_yolo\views.py:204
- 示例响应(节选):
```json
{
"success": true,
"code": 200,
"message": "获取检测记录列表成功",
"data": [
{
"id": 1001,
"original_file": "/media/detection/original/uuid_image.jpg",
"result_file": "/media/detection/result/result_xxx.jpg",
"original_filename": "uuid_image.jpg",
"result_filename": "result_xxx.jpg",
"detection_type": "image",
"model_name": "ModelA 1.0",
"model_info": {"id":1, "name":"ModelA", "version":"1.0"},
"object_count": 3,
"detected_categories": ["person"],
"confidence_threshold": 0.5,
"confidence_scores": [0.91, 0.87, 0.79],
"avg_confidence": 0.8567,
"processing_time": 0.43,
"created_at": "..."
}
]
}
```
### 2获取指定用户的检测记录
- 方法: `GET`
- 路径: `/api/yolo/detections/{user_id}/user/`
- 认证: 不需要
- 查询参数同上
- 参考实现: `user_detection_records`d:\AllTemplate\yolo\hertz_studio_django_yolo\views.py:231
### 3获取检测记录详情
- 方法: `GET`
- 路径: `/api/yolo/detections/{pk}/`
- 认证: 不需要
- 参考实现: `detection_detail`d:\AllTemplate\yolo\hertz_studio_django_yolo\views.py:253
### 4删除检测记录
- 方法: `DELETE`
- 路径: `/api/yolo/detections/{pk}/delete/`
- 认证: 不需要
- 行为: 同时删除其关联的原始文件、结果文件及关联的告警
- 参考实现: `detection_delete`d:\AllTemplate\yolo\hertz_studio_django_yolo\views.py:265
- 示例响应:
```json
{"success": true, "code": 200, "message": "检测记录删除成功"}
```
### 5批量删除检测记录
- 方法: `POST`
- 路径: `/api/yolo/detections/batch-delete/`
- 认证: 不需要
- 请求类型: `application/json`
- 请求体:
```json
{"ids": [1001, 1002, 1003]}
```
- 参考实现: `detection_batch_delete`d:\AllTemplate\yolo\hertz_studio_django_yolo\views.py:299
- 示例响应:
```json
{
"success": true,
"code": 200,
"message": "成功删除 3 条检测记录",
"data": {
"deleted_count": 3,
"found_ids": ["1001","1002","1003"],
"not_found_ids": []
}
}
```
### 6检测统计
- 方法: `GET`
- 路径: `/api/yolo/stats/`
- 认证: 不需要
- 参考实现: `detection_stats`d:\AllTemplate\yolo\hertz_studio_django_yolo\views.py:840
## 六、告警记录
### 1获取告警记录列表管理员
- 方法: `GET`
- 路径: `/api/yolo/alerts/`
- 认证: 不需要
- 查询参数:
- `status`: 默认 `pending`;传 `all` 表示不过滤
- `level`: 告警等级(`high|medium|low|none`
- `user_id`: 用户ID
- `alter_category`: 告警类别关键字(注意字段名为 `alter_category`
- 参考实现: `alert_list`d:\AllTemplate\yolo\hertz_studio_django_yolo\views.py:358
### 2获取用户的告警记录
- 方法: `GET`
- 路径: `/api/yolo/users/{user_id}/alerts/`
- 认证: 需要登录(仅本人或管理员可查)
- 查询参数:
- `status`: `pending|is_confirm|false_positive|all`
- `level`: `high|medium|low|none`
- `category`: 类别关键字
- 参考实现: `user_alert_records`d:\AllTemplate\yolo\hertz_studio_django_yolo\views.py:391
### 3更新告警状态
- 方法: `PUT` 或 `PATCH`
- 路径: `/api/yolo/alerts/{pk}/update-status/`
- 认证: 不需要
- 请求类型: `application/json`
- 请求体:
```json
{"status": "is_confirm"}
```
- 可选值: `pending`, `is_confirm`, `false_positive`
- 参考实现: `alert_update_status`d:\AllTemplate\yolo\hertz_studio_django_yolo\views.py:426
- 示例响应:
```json
{
"success": true,
"code": 200,
"message": "更新告警状态成功",
"data": {
"id": 555,
"status": "is_confirm",
"alert_level": "medium",
"alert_category": "person",
"alert_level_display": "中"
}
}
```
## 七、备注
- 所有文件型字段响应通常包含可直接访问的媒体 URL媒体服务由 `MEDIA_URL=/media/` 提供。
- 分类的告警等级枚举参考 `ModelCategory.ALERT_LEVELS` 与 `Alert.ALERT_LEVELS`d:\AllTemplate\yolo\hertz_studio_django_yolo\models.py:118,153
- 检测请求的文件大小限制:图片 ≤ 50MB视频 ≤ 500MBd:\AllTemplate\yolo\hertz_studio_django_yolo\serializers.py:99

View File

@@ -0,0 +1,123 @@
# Hertz Studio Django 代码生成模块接口文档
- 基础路径: `/api/codegen/`
- 统一响应: 使用 `HertzResponse`,结构如下(参考 `hertz_studio_django_utils/responses/HertzResponse.py`
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {}
}
```
- 路由挂载: 项目主路由中已通过 `path('api/codegen/', include(('hertz_studio_django_codegen.urls', 'hertz_studio_django_codegen'), namespace='codegen'))` 挂载(`hertz_server_django/urls.py:51`)。
- 认证说明: 接口需要登录,需在请求头携带 `Authorization: Bearer <token>``hertz_studio_django_codegen/views/simple_generator_views.py:136`)。
## 一、简化代码生成
### 生成应用代码与菜单配置
- 方法: `POST`
- 路径: `/api/codegen/simple/generate/`
- 认证: 需要登录
- 实现: `simple_code_generate``hertz_studio_django_codegen/views/simple_generator_views.py:136`
- 请求体: `application/json`
- 字段:
- `module_name` 字符串,模块中文名,例如 `产品管理`
- `model_name` 字符串,模型英文名,例如 `Product`
- `app_name` 字符串Django 应用名称,例如 `hertz_studio_django_product`
- `fields` 数组,字段定义列表,每项包含:`name`、`type`、`verbose_name`、`help_text`、`required`、`max_length`、`choices`
- `operations` 数组,支持的操作,默认 `['list','create','update','delete']`
- `menu_config` 对象,菜单配置:`enabled`、`parent_code`、`prefix`、`sort_order`、`icon`
- `generate_app` 布尔,是否生成应用代码,默认 `true`
- `generate_menu` 布尔,是否生成菜单配置,默认 `true`
- 请求示例(同时生成应用与菜单):
```http
POST /api/codegen/simple/generate/
Authorization: Bearer <token>
Content-Type: application/json
{
"module_name": "产品管理",
"model_name": "Product",
"app_name": "hertz_studio_django_product",
"fields": [
{"name": "name", "type": "CharField", "verbose_name": "产品名称", "required": true, "max_length": 100},
{"name": "price", "type": "FloatField", "verbose_name": "价格", "required": true},
{"name": "status", "type": "IntegerField", "verbose_name": "状态", "choices": [[0,"下线"],[1,"上线"]]}
],
"operations": ["list","create","update","delete"],
"menu_config": {"enabled": true, "parent_code": "system", "prefix": "product", "sort_order": 10, "icon": "box"},
"generate_app": true,
"generate_menu": true
}
```
- 返回示例(成功):
```json
{
"success": true,
"code": 200,
"message": "成功生成产品管理模块代码和菜单配置",
"data": {
"generated_files": {
"hertz_studio_django_product/models.py": "...",
"hertz_studio_django_product/serializers.py": "...",
"hertz_studio_django_product/views.py": "...",
"hertz_studio_django_product/urls.py": "...",
"hertz_studio_django_product/apps.py": "..."
},
"menu_configs": [
{"code": "product", "name": "产品管理", "type": "menu", "sort_order": 10},
{"code": "product:list", "name": "产品列表", "type": "permission", "sort_order": 11}
],
"app_path": "hertz_studio_django_product",
"menu_file": "d:/All_template/yolo/pending_menus_product.py"
}
}
```
- 请求示例(仅生成菜单配置):
```http
POST /api/codegen/simple/generate/
Authorization: Bearer <token>
Content-Type: application/json
{
"module_name": "库存管理",
"model_name": "Inventory",
"app_name": "hertz_studio_django_inventory",
"fields": [],
"operations": ["list"],
"menu_config": {"enabled": true, "parent_code": "system", "prefix": "inventory"},
"generate_app": false,
"generate_menu": true
}
```
- 返回示例(仅菜单):
```json
{
"success": true,
"code": 200,
"message": "成功生成库存管理模块代码和菜单配置",
"data": {
"generated_files": {},
"menu_configs": [{"code": "inventory", "name": "库存管理", "type": "menu"}],
"app_path": "",
"menu_file": "d:/All_template/yolo/pending_menus_inventory.py"
}
}
```
## 二、错误响应示例
- 缺少必填参数:
```json
{"success": false, "code": 422, "message": "缺少必填参数: fields"}
```
- 请求体格式错误非JSON:
```json
{"success": false, "code": 422, "message": "请求参数格式错误请使用JSON格式"}
```
- 生成失败(异常信息):
```json
{"success": false, "code": 500, "message": "代码生成失败: <错误信息>"}
```

View File

@@ -0,0 +1,121 @@
# Hertz Studio Django 日志模块接口文档
- 基础路径: `/api/log/`
- 统一响应: 使用 `HertzResponse`,结构如下(参考 `hertz_studio_django_utils/responses/HertzResponse.py`
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {}
}
```
- 路由挂载: 项目主路由中已通过 `path('api/log/', include('hertz_studio_django_log.urls'))` 挂载(`hertz_server_django/urls.py:46`)。
- 认证与权限: 接口需要登录并具备相应权限,其中列表接口需 `system:log:list`,详情接口需 `system:log:query``hertz_studio_django_log/views/log_views.py:18`, `225`)。
## 一、操作日志列表
### 获取操作日志列表
- 方法: `GET`
- 路径: `/api/log/list/`
- 权限: 仅管理员(`system:log:list`
- 查询参数:
- `user_id`: 用户ID
- `username`: 用户名(模糊匹配)
- `action_type`: 操作类型(`create|update|delete|view|list|login|logout|export|import|other`
- `module`: 操作模块(模糊匹配)
- `status`: 操作状态(`0|1`
- `ip_address`: IP地址
- `start_date`: 开始时间ISO日期时间
- `end_date`: 结束时间ISO日期时间
- `page`: 页码,默认 `1`
- `page_size`: 每页数量,默认 `20`
- 实现: `operation_log_list``hertz_studio_django_log/views/log_views.py:117`
- 请求示例:
```bash
curl "http://localhost:8000/api/log/list/?username=admin&action_type=login&page=1&page_size=10" \
-H "Authorization: Bearer <token>"
```
- 返回示例(节选):
```json
{
"success": true,
"code": 200,
"message": "获取操作日志列表成功",
"data": {
"logs": [
{
"log_id": 1001,
"username": "admin",
"action_type": "login",
"action_type_display": "登录",
"module": "认证",
"description": "用户登录",
"ip_address": "127.0.0.1",
"response_status": 200,
"status": 1,
"status_display": "成功",
"is_success": true,
"created_at": "2025-11-17T08:30:00Z"
}
],
"pagination": {
"page": 1,
"page_size": 10,
"total_count": 1,
"total_pages": 1,
"has_next": false,
"has_previous": false
}
}
}
```
## 二、操作日志详情
### 获取操作日志详情
- 方法: `GET`
- 路径: `/api/log/detail/{log_id}/`
- 权限: 仅管理员(`system:log:query`
- 路径参数: `log_id` 日志ID
- 实现: `operation_log_detail``hertz_studio_django_log/views/log_views.py:259`
- 请求示例:
```bash
curl "http://localhost:8000/api/log/detail/1001/" \
-H "Authorization: Bearer <token>"
```
- 返回示例(节选):
```json
{
"success": true,
"code": 200,
"message": "获取操作日志详情成功",
"data": {
"log_id": 1001,
"user": 1,
"username": "admin",
"action_type": "login",
"action_type_display": "登录",
"module": "认证",
"description": "用户登录",
"target_model": null,
"target_id": null,
"ip_address": "127.0.0.1",
"user_agent": "Mozilla/5.0",
"request_data": {"username": "admin"},
"formatted_request_data": "{\n \"username\": \"admin\"\n}",
"response_status": 200,
"status": 1,
"status_display": "成功",
"is_success": true,
"created_at": "2025-11-17T08:30:00Z"
}
}
```
## 三、备注
- 列表返回采用 `OperationLogListSerializer` 字段子集以简化展示。
- 详情返回使用 `OperationLogSerializer`,包含格式化的请求数据与成功判断等辅助字段。

View File

@@ -0,0 +1,302 @@
# Hertz Studio Django 系统监控模块接口文档
- 基础路径: `/api/system/`
- 统一响应: 使用 `HertzResponse`,结构如下(参考 `hertz_studio_django_utils/responses/HertzResponse.py`
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {}
}
```
- 路由挂载: 项目主路由中已通过 `path('api/system/', include('hertz_studio_django_system_monitor.urls'))` 挂载(`hertz_server_django/urls.py:23`)。
- 认证说明: 所有接口均需要登录,需在请求头携带 `Authorization: Bearer <token>``hertz_studio_django_auth/utils/decorators/auth_decorators.py:1`)。
## 一、系统信息
### 获取系统信息
- 方法: `GET`
- 路径: `/api/system/system/`
- 认证: 需要登录
- 实现: `SystemInfoView.get``hertz_studio_django_system_monitor/views.py:63`
- 请求示例:
```bash
curl "http://localhost:8000/api/system/system/" \
-H "Authorization: Bearer <token>"
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"hostname": "DESKTOP-ABC123",
"platform": "Windows-10-10.0.19041-SP0",
"architecture": "AMD64",
"boot_time": "2025-11-16T08:30:00Z",
"uptime": "2 days, 14:30:00"
}
}
```
## 二、CPU 信息
### 获取CPU信息
- 方法: `GET`
- 路径: `/api/system/cpu/`
- 认证: 需要登录
- 实现: `CPUInfoView.get``hertz_studio_django_system_monitor/views.py:63`
- 请求示例:
```bash
curl "http://localhost:8000/api/system/cpu/" \
-H "Authorization: Bearer <token>"
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"cpu_count": 8,
"cpu_percent": 25.6,
"cpu_freq": {"current": 2400.0, "min": 800.0, "max": 3600.0},
"load_avg": [1.2, 1.5, 1.8]
}
}
```
## 三、内存信息
### 获取内存信息
- 方法: `GET`
- 路径: `/api/system/memory/`
- 认证: 需要登录
- 实现: `MemoryInfoView.get``hertz_studio_django_system_monitor/views.py:94`
- 请求示例:
```bash
curl "http://localhost:8000/api/system/memory/" \
-H "Authorization: Bearer <token>"
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"total": 17179869184,
"available": 8589934592,
"used": 8589934592,
"percent": 50.0,
"free": 8589934592
}
}
```
## 四、磁盘信息
### 获取磁盘信息
- 方法: `GET`
- 路径: `/api/system/disks/`
- 认证: 需要登录
- 实现: `DiskInfoView.get``hertz_studio_django_system_monitor/views.py:127`
- 请求示例:
```bash
curl "http://localhost:8000/api/system/disks/" \
-H "Authorization: Bearer <token>"
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": [
{
"device": "C:\\",
"mountpoint": "C:\\",
"fstype": "NTFS",
"total": 1073741824000,
"used": 536870912000,
"free": 536870912000,
"percent": 50.0
}
]
}
```
## 五、网络信息
### 获取网络信息
- 方法: `GET`
- 路径: `/api/system/network/`
- 认证: 需要登录
- 实现: `NetworkInfoView.get``hertz_studio_django_system_monitor/views.py:170`
- 请求示例:
```bash
curl "http://localhost:8000/api/system/network/" \
-H "Authorization: Bearer <token>"
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": [
{
"interface": "以太网",
"bytes_sent": 1048576000,
"bytes_recv": 2097152000,
"packets_sent": 1000000,
"packets_recv": 1500000
}
]
}
```
## 六、进程信息
### 获取进程信息
- 方法: `GET`
- 路径: `/api/system/processes/`
- 认证: 需要登录
- 查询参数:
- `limit`: 返回条数,默认 `20`
- `sort_by`: 排序字段,默认 `cpu_percent`(可选 `cpu_percent|memory_percent|create_time`
- 实现: `ProcessInfoView.get``hertz_studio_django_system_monitor/views.py:204`
- 请求示例:
```bash
curl "http://localhost:8000/api/system/processes/?limit=10&sort_by=cpu_percent" \
-H "Authorization: Bearer <token>"
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": [
{
"pid": 1234,
"name": "python.exe",
"status": "running",
"cpu_percent": 15.6,
"memory_percent": 8.2,
"memory_info": {"rss": 134217728, "vms": 268435456},
"create_time": "2025-11-16T10:30:00Z",
"cmdline": ["python", "manage.py", "runserver"]
}
]
}
```
## 七、GPU 信息
### 获取GPU信息
- 方法: `GET`
- 路径: `/api/system/gpu/`
- 认证: 需要登录
- 实现: `GPUInfoView.get``hertz_studio_django_system_monitor/views.py:259`
- 请求示例:
```bash
curl "http://localhost:8000/api/system/gpu/" \
-H "Authorization: Bearer <token>"
```
- 返回示例有GPU设备:
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"gpu_available": true,
"gpu_info": [
{
"id": 0,
"name": "NVIDIA GeForce RTX 3080",
"load": 45.6,
"memory_total": 10240,
"memory_used": 4096,
"memory_util": 40.0,
"temperature": 65
}
],
"timestamp": "2025-11-16 18:30:00"
}
}
```
- 返回示例无GPU设备:
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"gpu_available": false,
"message": "未检测到GPU设备",
"timestamp": "2025-11-16 18:30:00"
}
}
```
- 返回示例GPU库不可用:
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"gpu_available": false,
"message": "GPU监控不可用请安装GPUtil库",
"timestamp": "2025-11-16 18:30:00"
}
}
```
## 八、综合监控
### 获取系统监测综合信息
- 方法: `GET`
- 路径: `/api/system/monitor/`
- 认证: 需要登录
- 实现: `SystemMonitorView.get``hertz_studio_django_system_monitor/views.py:325`
- 请求示例:
```bash
curl "http://localhost:8000/api/system/monitor/" \
-H "Authorization: Bearer <token>"
```
- 返回示例(节选):
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"system": {"hostname": "DESKTOP-ABC123", "platform": "Windows-10-10.0.19041-SP0", "architecture": "AMD64", "boot_time": "2025-11-16T08:30:00Z", "uptime": "2 days, 14:30:00"},
"cpu": {"cpu_count": 8, "cpu_percent": 25.6, "cpu_freq": {"current": 2400.0, "min": 800.0, "max": 3600.0}, "load_avg": [1.2, 1.5, 1.8]},
"memory": {"total": 17179869184, "available": 8589934592, "used": 8589934592, "percent": 50.0, "free": 8589934592},
"disks": [{"device": "C:\\", "mountpoint": "C:\\", "fstype": "NTFS", "total": 1073741824000, "used": 536870912000, "free": 536870912000, "percent": 50.0}],
"network": [{"interface": "以太网", "bytes_sent": 1048576000, "bytes_recv": 2097152000, "packets_sent": 1000000, "packets_recv": 1500000}],
"processes": [{"pid": 1234, "name": "python.exe", "status": "running", "cpu_percent": 15.6, "memory_percent": 8.2, "memory_info": {"rss": 134217728, "vms": 268435456}, "create_time": "2025-11-16T10:30:00Z", "cmdline": ["python", "manage.py", "runserver"]}],
"gpus": [{"id": 0, "name": "NVIDIA GeForce RTX 3080", "load": 45.6, "memory_total": 10240, "memory_used": 4096, "memory_util": 40.0, "temperature": 65}]
}
}
```
## 九、错误响应示例
- 通用错误格式:
```json
{"success": false, "code": 401, "message": "未授权访问"}
```

View File

@@ -0,0 +1,452 @@
# Hertz Studio Django 认证模块接口文档
- 基础路径: `/api/`
- 统一响应: 使用 `HertzResponse`,结构如下(参考 `hertz_studio_django_utils/responses/HertzResponse.py`
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {}
}
```
- 路由挂载: 项目主路由中已通过 `path('api/', include('hertz_studio_django_auth.urls'))` 挂载(`hertz_server_django/urls.py:31`)。
- 认证说明:
- 登录、注册、发送邮箱验证码、重置密码:不需要登录
- 其他接口需要在请求头携带 `Authorization: Bearer <access_token>``hertz_studio_django_auth/utils/decorators/auth_decorators.py:24`)。
- 路由前缀说明:
- 认证相关接口前缀为 `/api/auth/`
- 管理接口(用户/角色/菜单/部门)前缀为 `/api/`
## 一、用户认证
### 1用户登录
- 方法: `POST`
- 路径: `/api/auth/login/`
- 认证: 不需要登录
- 请求体: `application/json`
- 字段: `username`, `password`, `captcha_code`, `captcha_key`
- 实现: `user_login``hertz_studio_django_auth/views/auth_views.py:132`
- 请求示例:
```http
POST /api/auth/login/
Content-Type: application/json
{
"username": "admin",
"password": "Passw0rd!",
"captcha_code": "A1B2",
"captcha_key": "c1a2b3c4-d5e6-7890-abcd-ef1234567890"
}
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "登录成功",
"data": {
"access_token": "eyJhbGci...",
"refresh_token": "eyJhbGci...",
"user_info": {
"user_id": 1,
"username": "admin",
"email": "admin@example.com",
"phone": "13800000000",
"real_name": "管理员",
"avatar": null,
"roles": [{"role_id": 1, "role_name": "管理员", "role_code": "admin"}],
"permissions": ["system:user:list", "system:role:list"]
}
}
}
```
### 2用户注册
- 方法: `POST`
- 路径: `/api/auth/register/`
- 认证: 不需要登录
- 请求体: `application/json`
- 字段: `username`, `password`, `confirm_password`, `email`, `phone`, `real_name`, `email_code?`
- 实现: `user_register``hertz_studio_django_auth/views/auth_views.py:131`
- 请求示例:
```http
POST /api/auth/register/
Content-Type: application/json
{
"username": "newuser",
"password": "Passw0rd!",
"confirm_password": "Passw0rd!",
"email": "new@example.com",
"phone": "13800000001",
"real_name": "新用户",
"email_code": "123456"
}
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "注册成功", "data": {"user_id": 12, "username": "newuser"}}
```
### 3用户登出
- 方法: `POST`
- 路径: `/api/auth/logout/`
- 认证: 需要登录
- 实现: `user_logout``hertz_studio_django_auth/views/auth_views.py:184`
- 请求示例:
```bash
curl -X POST "http://localhost:8000/api/auth/logout/" -H "Authorization: Bearer <access_token>"
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "登出成功"}
```
### 4修改密码
- 方法: `POST`
- 路径: `/api/auth/password/change/`
- 认证: 需要登录
- 请求体: `application/json`
- 字段: `old_password`, `new_password`, `confirm_password`
- 实现: `change_password``hertz_studio_django_auth/views/auth_views.py:214`
- 请求示例:
```http
POST /api/auth/password/change/
Authorization: Bearer <access_token>
Content-Type: application/json
{"old_password": "Passw0rd!", "new_password": "N3wPass!", "confirm_password": "N3wPass!"}
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "密码修改成功"}
```
### 5重置密码
- 方法: `POST`
- 路径: `/api/auth/password/reset/`
- 认证: 不需要登录
- 请求体: `application/json`
- 字段: `email`, `email_code`, `new_password`, `confirm_password`
- 实现: `reset_password``hertz_studio_django_auth/views/auth_views.py:259`
- 请求示例:
```http
POST /api/auth/password/reset/
Content-Type: application/json
{"email": "user@example.com", "email_code": "654321", "new_password": "N3wPass!", "confirm_password": "N3wPass!"}
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "密码重置成功"}
```
### 6获取当前用户信息
- 方法: `GET`
- 路径: `/api/auth/user/info/`
- 认证: 需要登录
- 实现: `get_user_info``hertz_studio_django_auth/views/auth_views.py:310`
- 请求示例:
```bash
curl "http://localhost:8000/api/auth/user/info/" -H "Authorization: Bearer <access_token>"
```
- 返回示例(节选):
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {
"user_id": 1,
"username": "admin",
"email": "admin@example.com",
"phone": "13800000000",
"real_name": "管理员",
"department_id": 2,
"department_name": "技术部",
"roles": [{"role_id": 1, "role_name": "管理员", "role_code": "admin"}],
"last_login_time": "2025-11-17T09:00:00Z"
}
}
```
### 7更新当前用户信息
- 方法: `PUT`
- 路径: `/api/auth/user/info/update/`
- 认证: 需要登录
- 请求体: `application/json`
- 字段: `email`, `phone`, `real_name`, `avatar`, `gender`, `birthday`
- 实现: `update_user_info``hertz_studio_django_auth/views/auth_views.py:339`
- 请求示例:
```http
PUT /api/auth/user/info/update/
Authorization: Bearer <access_token>
Content-Type: application/json
{"real_name": "张三", "phone": "13800000002"}
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "用户信息更新成功", "data": {"real_name": "张三", "phone": "13800000002"}}
```
### 8获取用户菜单
- 方法: `GET`
- 路径: `/api/auth/user/menus/`
- 认证: 需要登录
- 实现: `get_user_menus``hertz_studio_django_auth/views/auth_views.py:368`
- 请求示例:
```bash
curl "http://localhost:8000/api/auth/user/menus/" -H "Authorization: Bearer <access_token>"
```
- 返回示例(树形结构):
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": [
{"menu_id": 1, "menu_name": "系统管理", "menu_code": "system", "menu_type": 1, "path": "/system", "children": [
{"menu_id": 2, "menu_name": "用户管理", "menu_code": "system:user", "menu_type": 2, "path": "/system/users", "children": []}
]}
]
}
```
### 9发送邮箱验证码
- 方法: `POST`
- 路径: `/api/auth/email/code/`
- 认证: 不需要登录
- 请求体: `application/json`
- 字段: `email`, `code_type`(如 `register|reset_password`
- 实现: `send_email_code``hertz_studio_django_auth/views/auth_views.py:441`
- 请求示例:
```http
POST /api/auth/email/code/
Content-Type: application/json
{"email": "user@example.com", "code_type": "register"}
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "验证码发送成功,请查收邮件", "data": {"email": "user@example.com", "code_type": "register", "expires_in": 300}}
```
### 10刷新访问令牌
- 方法: `POST`
- 路径: `/api/auth/token/refresh/`
- 认证: 需要登录
- 请求体: `application/json`
- 字段: `refresh_token`
- 实现: `refresh_token``hertz_studio_django_auth/views/auth_views.py:544`
- 请求示例:
```http
POST /api/auth/token/refresh/
Authorization: Bearer <access_token>
Content-Type: application/json
{"refresh_token": "eyJhbGci..."}
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "token刷新成功", "data": {"access_token": "eyJhbGci..."}}
```
## 二、用户管理
- 前缀: `/api/`
### 1获取用户列表
- 方法: `GET`
- 路径: `/api/users/`
- 权限: `system:user:list`
- 查询参数: `page`, `page_size`, `username`, `email`, `real_name`, `department_id`, `status`
- 实现: `user_list``hertz_studio_django_auth/views/management_views.py:38`
### 2创建用户
- 方法: `POST`
- 路径: `/api/users/create/`
- 权限: `system:user:add`
- 请求体: `UserManagementSerializer` 字段
- 实现: `user_create``hertz_studio_django_auth/views/management_views.py:106`
### 3获取用户详情
- 方法: `GET`
- 路径: `/api/users/{user_id}/`
- 权限: `system:user:query`
- 实现: `user_detail``hertz_studio_django_auth/views/management_views.py:149`
### 4更新用户
- 方法: `PUT`
- 路径: `/api/users/{user_id}/update/`
- 权限: `system:user:edit`
- 请求体: `UserManagementSerializer`
- 实现: `user_update``hertz_studio_django_auth/views/management_views.py:190`
### 5删除用户
- 方法: `DELETE`
- 路径: `/api/users/{user_id}/delete/`
- 权限: `system:user:remove`
- 实现: `user_delete``hertz_studio_django_auth/views/management_views.py:236`
### 6分配用户角色
- 方法: `POST`
- 路径: `/api/users/assign-roles/`
- 权限: `system:user:role`
- 请求体: `user_id`, `role_ids: number[]`
- 实现: `user_assign_roles``hertz_studio_django_auth/views/management_views.py:279`
## 三、角色管理
- 前缀: `/api/`
### 1获取角色列表
- 方法: `GET`
- 路径: `/api/roles/`
- 权限: `system:role:list`
- 查询参数: `page`, `page_size`, `role_name`, `role_code`, `status`
- 实现: `role_list``hertz_studio_django_auth/views/management_views.py:328`
### 2创建角色
- 方法: `POST`
- 路径: `/api/roles/create/`
- 权限: `system:role:add`
- 请求体: `RoleManagementSerializer`
- 实现: `role_create``hertz_studio_django_auth/views/management_views.py:393`
### 3获取角色详情
- 方法: `GET`
- 路径: `/api/roles/{role_id}/`
- 权限: `system:role:query`
- 实现: `role_detail``hertz_studio_django_auth/views/management_views.py:437`
### 4更新角色
- 方法: `PUT`
- 路径: `/api/roles/{role_id}/update/`
- 权限: `system:role:edit`
- 请求体: `RoleManagementSerializer`
- 实现: `role_update``hertz_studio_django_auth/views/management_views.py:477`
### 5删除角色
- 方法: `DELETE`
- 路径: `/api/roles/{role_id}/delete/`
- 权限: `system:role:remove`
- 实现: `role_delete``hertz_studio_django_auth/views/management_views.py:527`
### 6分配角色菜单
- 方法: `POST`
- 路径: `/api/roles/assign-menus/`
- 权限: `system:role:menu`
- 请求体: `role_id`, `menu_ids: number[]`
- 实现: `role_assign_menus``hertz_studio_django_auth/views/management_views.py:579`
### 7获取角色菜单
- 方法: `GET`
- 路径: `/api/roles/{role_id}/menus/`
- 权限: `system:role:menu`
- 实现: `role_menus``hertz_studio_django_auth/views/management_views.py:631`
## 四、菜单管理
- 前缀: `/api/`
### 1获取菜单列表
- 方法: `GET`
- 路径: `/api/menus/`
- 权限: `system:menu:list`
- 实现: `menu_list``hertz_studio_django_auth/views/management_views.py:665`
### 2获取菜单树
- 方法: `GET`
- 路径: `/api/menus/tree/`
- 权限: `system:menu:list`
- 实现: `menu_tree``hertz_studio_django_auth/views/management_views.py:710`
### 3创建菜单
- 方法: `POST`
- 路径: `/api/menus/create/`
- 权限: `system:menu:add`
- 请求体: `MenuManagementSerializer`
- 实现: `menu_create``hertz_studio_django_auth/views/management_views.py:744`
### 4获取菜单详情
- 方法: `GET`
- 路径: `/api/menus/{menu_id}/`
- 权限: `system:menu:query`
- 实现: `menu_detail``hertz_studio_django_auth/views/management_views.py:787`
### 5更新菜单
- 方法: `PUT`
- 路径: `/api/menus/{menu_id}/update/`
- 权限: `system:menu:edit`
- 请求体: `MenuManagementSerializer`
- 实现: `menu_update``hertz_studio_django_auth/views/management_views.py:828`
### 6删除菜单
- 方法: `DELETE`
- 路径: `/api/menus/{menu_id}/delete/`
- 权限: `system:menu:remove`
- 实现: `menu_delete``hertz_studio_django_auth/views/management_views.py:878`
## 五、部门管理
- 前缀: `/api/`
### 1获取部门列表
- 方法: `GET`
- 路径: `/api/departments/`
- 权限: `system:dept:list`
- 实现: `department_list``hertz_studio_django_auth/views/management_views.py:921`
### 2获取部门树
- 方法: `GET`
- 路径: `/api/departments/tree/`
- 权限: `system:dept:list`
- 实现: `department_tree``hertz_studio_django_auth/views/management_views.py:963`
### 3创建部门
- 方法: `POST`
- 路径: `/api/departments/create/`
- 权限: `system:dept:add`
- 请求体: `DepartmentManagementSerializer`
- 实现: `department_create``hertz_studio_django_auth/views/management_views.py:997`
### 4获取部门详情
- 方法: `GET`
- 路径: `/api/departments/{dept_id}/`
- 权限: `system:dept:query`
- 实现: `department_detail``hertz_studio_django_auth/views/management_views.py:1040`
### 5更新部门
- 方法: `PUT`
- 路径: `/api/departments/{dept_id}/update/`
- 权限: `system:dept:edit`
- 请求体: `DepartmentManagementSerializer`
- 实现: `department_update``hertz_studio_django_auth/views/management_views.py:1081`
### 6删除部门
- 方法: `DELETE`
- 路径: `/api/departments/{dept_id}/delete/`
- 权限: `system:dept:remove`
- 实现: `department_delete``hertz_studio_django_auth/views/management_views.py:1131`
## 六、错误响应示例
- 未授权访问:
```json
{"success": false, "code": 401, "message": "未提供认证令牌"}
```
- 权限不足:
```json
{"success": false, "code": 403, "message": "缺少权限system:user:list"}
```
- 参数验证失败:
```json
{"success": false, "code": 422, "message": "参数验证失败", "errors": {"email": ["邮箱格式不正确"]}}
```

View File

@@ -0,0 +1,391 @@
# Hertz Studio Django 通知模块接口文档
- 基础路径: `/api/notice/`
- 统一响应: 使用 `HertzResponse`,结构如下(参考 `hertz_studio_django_utils/responses/HertzResponse.py`
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {}
}
```
- 路由挂载: 项目主路由中已通过 `path('api/notice/', include('hertz_studio_django_notice.urls'))` 挂载(`hertz_server_django/urls.py:19`)。
- 认证说明: 所有接口均需要登录,需在请求头携带 `Authorization: Bearer <token>``hertz_studio_django_auth/utils/decorators/auth_decorators.py:1`)。
## 一、管理员通知接口
### 1创建通知
- 方法: `POST`
- 路径: `/api/notice/admin/create/`
- 认证: 需要登录
- 请求体: `application/json`
- 字段: `title`, `content`, `notice_type`, `priority`, `is_top`, `publish_time`, `expire_time`, `attachment_url`
- 实现: `admin_create_notice``hertz_studio_django_notice/views/admin_views.py:39`
- 请求示例:
```http
POST /api/notice/admin/create/
Authorization: Bearer <token>
Content-Type: application/json
{
"title": "系统维护通知",
"content": "将于周六晚间进行系统维护。",
"notice_type": 1,
"priority": 3,
"is_top": true,
"publish_time": "2025-11-18 09:00:00",
"expire_time": "2025-11-20 23:59:59",
"attachment_url": null
}
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "通知创建成功", "data": {"notice_id": 101}}
```
### 2更新通知
- 方法: `PUT`
- 路径: `/api/notice/admin/update/{notice_id}/`
- 认证: 需要登录
- 请求体: `application/json`
- 字段: `title`, `content`, `notice_type`, `priority`, `is_top`, `publish_time`, `expire_time`, `attachment_url`, `status`
- 实现: `admin_update_notice``hertz_studio_django_notice/views/admin_views.py:94`
- 请求示例:
```http
PUT /api/notice/admin/update/101/
Authorization: Bearer <token>
Content-Type: application/json
{"priority": 4, "is_top": false}
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "通知更新成功"}
```
### 3删除通知
- 方法: `DELETE`
- 路径: `/api/notice/admin/delete/{notice_id}/`
- 认证: 需要登录
- 实现: `admin_delete_notice``hertz_studio_django_notice/views/admin_views.py:146`
- 请求示例:
```bash
curl -X DELETE "http://localhost:8000/api/notice/admin/delete/101/" \
-H "Authorization: Bearer <token>"
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "通知删除成功"}
```
### 4获取通知列表
- 方法: `GET`
- 路径: `/api/notice/admin/list/`
- 认证: 需要登录
- 查询参数:
- `page`, `page_size`
- `notice_type`, `status`, `priority`, `is_top`, `keyword`
- `start_date`, `end_date`(按发布时间范围筛选)
- 实现: `admin_get_notice_list``hertz_studio_django_notice/views/admin_views.py:184`
- 请求示例:
```bash
curl "http://localhost:8000/api/notice/admin/list/?page=1&page_size=10&status=1&is_top=true" \
-H "Authorization: Bearer <token>"
```
- 返回示例(节选):
```json
{
"success": true,
"code": 200,
"message": "获取通知列表成功",
"data": {
"notices": [
{
"notice_id": 101,
"title": "系统维护通知",
"notice_type": 1,
"notice_type_display": "系统通知",
"priority": 3,
"priority_display": "高",
"status": 1,
"status_display": "已发布",
"is_top": true,
"publish_time": "2025-11-18T09:00:00Z",
"expire_time": "2025-11-20T23:59:59Z",
"publisher_name": "管理员",
"view_count": 12,
"is_expired": false,
"read_count": 30,
"unread_count": 5,
"created_at": "2025-11-17T10:00:00Z",
"updated_at": "2025-11-17T10:00:00Z"
}
],
"pagination": {"current_page": 1, "page_size": 10, "total_pages": 1, "total_count": 1, "has_next": false, "has_previous": false}
}
}
```
### 5获取通知详情
- 方法: `GET`
- 路径: `/api/notice/admin/detail/{notice_id}/`
- 认证: 需要登录
- 实现: `admin_get_notice_detail``hertz_studio_django_notice/views/admin_views.py:273`
- 请求示例:
```bash
curl "http://localhost:8000/api/notice/admin/detail/101/" -H "Authorization: Bearer <token>"
```
- 返回示例(节选):
```json
{
"success": true,
"code": 200,
"message": "获取通知详情成功",
"data": {
"notice_id": 101,
"title": "系统维护通知",
"content": "将于周六晚间进行系统维护。",
"notice_type": 1,
"notice_type_display": "系统通知",
"priority": 3,
"priority_display": "高",
"status": 1,
"status_display": "已发布",
"is_top": true,
"publish_time": "2025-11-18T09:00:00Z",
"expire_time": "2025-11-20T23:59:59Z",
"attachment_url": null,
"publisher_name": "管理员",
"publisher_username": "admin",
"view_count": 12,
"is_expired": false,
"read_count": 30,
"unread_count": 5,
"created_at": "2025-11-17T10:00:00Z",
"updated_at": "2025-11-17T10:00:00Z"
}
}
```
### 6发布通知
- 方法: `POST`
- 路径: `/api/notice/admin/publish/{notice_id}/`
- 认证: 需要登录
- 实现: `admin_publish_notice``hertz_studio_django_notice/views/admin_views.py:317`
- 请求示例:
```bash
curl -X POST "http://localhost:8000/api/notice/admin/publish/101/" -H "Authorization: Bearer <token>"
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "通知发布成功"}
```
### 7撤回通知
- 方法: `POST`
- 路径: `/api/notice/admin/withdraw/{notice_id}/`
- 认证: 需要登录
- 实现: `admin_withdraw_notice``hertz_studio_django_notice/views/admin_views.py:374`
- 请求示例:
```bash
curl -X POST "http://localhost:8000/api/notice/admin/withdraw/101/" -H "Authorization: Bearer <token>"
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "通知撤回成功"}
```
## 二、用户通知接口
### 1获取通知列表
- 方法: `GET`
- 路径: `/api/notice/user/list/`
- 认证: 需要登录
- 查询参数:
- `page`, `page_size`
- `notice_type`, `is_read`, `is_starred`, `priority`, `keyword`
- `show_expired`: `true/false`,默认不显示过期通知
- 实现: `user_get_notice_list``hertz_studio_django_notice/views/user_views.py:28`
- 请求示例:
```bash
curl "http://localhost:8000/api/notice/user/list/?page=1&page_size=10&is_read=false&is_starred=true" \
-H "Authorization: Bearer <token>"
```
- 返回示例(节选):
```json
{
"success": true,
"code": 200,
"message": "获取通知列表成功",
"data": {
"notices": [
{
"title": "系统维护通知",
"notice_type_display": "系统通知",
"priority_display": "高",
"is_top": true,
"publish_time": "2025-11-18T09:00:00Z",
"is_read": false,
"read_time": null,
"is_starred": true,
"starred_time": "2025-11-17T12:00:00Z",
"is_expired": false,
"created_at": "2025-11-17T10:00:00Z"
}
],
"pagination": {
"current_page": 1,
"page_size": 10,
"total_pages": 1,
"total_count": 1,
"has_next": false,
"has_previous": false
},
"statistics": {"total_count": 10, "unread_count": 2, "starred_count": 3}
}
}
```
### 2获取通知详情
- 方法: `GET`
- 路径: `/api/notice/user/detail/{notice_id}/`
- 认证: 需要登录
- 行为: 自动标记为已读并增加查看次数
- 实现: `user_get_notice_detail``hertz_studio_django_notice/views/user_views.py:147`
- 请求示例:
```bash
curl "http://localhost:8000/api/notice/user/detail/101/" -H "Authorization: Bearer <token>"
```
- 返回示例(节选):
```json
{
"success": true,
"code": 200,
"message": "获取通知详情成功",
"data": {
"title": "系统维护通知",
"content": "将于周六晚间进行系统维护。",
"notice_type_display": "系统通知",
"priority_display": "高",
"attachment_url": null,
"publish_time": "2025-11-18T09:00:00Z",
"expire_time": "2025-11-20T23:59:59Z",
"is_top": true,
"is_expired": false,
"publisher_name": "管理员",
"is_read": true,
"read_time": "2025-11-17T12:05:00Z",
"is_starred": true,
"starred_time": "2025-11-17T12:00:00Z"
}
}
```
### 3标记通知已读
- 方法: `POST`
- 路径: `/api/notice/user/mark-read/`
- 认证: 需要登录
- 请求体: `application/json`
- 字段: `notice_id`
- 实现: `user_mark_notice_read``hertz_studio_django_notice/views/user_views.py:214`
- 请求示例:
```http
POST /api/notice/user/mark-read/
Authorization: Bearer <token>
Content-Type: application/json
{"notice_id": 101}
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "标记已读成功"}
```
### 4批量标记已读
- 方法: `POST`
- 路径: `/api/notice/user/batch-mark-read/`
- 认证: 需要登录
- 请求体: `application/json`
- 字段: `notice_ids: number[]`
- 实现: `user_batch_mark_read``hertz_studio_django_notice/views/user_views.py:260`
- 请求示例:
```http
POST /api/notice/user/batch-mark-read/
Authorization: Bearer <token>
Content-Type: application/json
{"notice_ids": [101, 102, 103]}
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "批量标记已读成功"}
```
### 5标记全部已读
- 方法: `POST`
- 路径: `/api/notice/user/mark-all-read/`
- 认证: 需要登录
- 实现: `user_mark_all_read``hertz_studio_django_notice/views/user_views.py:317`
- 请求示例:
```bash
curl -X POST "http://localhost:8000/api/notice/user/mark-all-read/" -H "Authorization: Bearer <token>"
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "标记全部已读成功共标记3条通知", "data": {"updated_count": 3}}
```
### 6切换收藏状态
- 方法: `POST`
- 路径: `/api/notice/user/toggle-star/`
- 认证: 需要登录
- 请求体: `application/json`
- 字段: `notice_id`, `is_starred`
- 实现: `user_toggle_notice_star``hertz_studio_django_notice/views/user_views.py:401`
- 请求示例:
```http
POST /api/notice/user/toggle-star/
Authorization: Bearer <token>
Content-Type: application/json
{"notice_id": 101, "is_starred": true}
```
- 返回示例:
```json
{"success": true, "code": 200, "message": "收藏成功"}
```
### 7获取通知统计信息
- 方法: `GET`
- 路径: `/api/notice/user/statistics/`
- 认证: 需要登录
- 实现: `user_get_notice_statistics``hertz_studio_django_notice/views/user_views.py:417`
- 请求示例:
```bash
curl "http://localhost:8000/api/notice/user/statistics/" -H "Authorization: Bearer <token>"
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "获取统计信息成功",
"data": {
"total_count": 10,
"unread_count": 2,
"read_count": 8,
"starred_count": 3,
"type_statistics": {"系统通知": 6, "公告通知": 3, "活动通知": 1, "维护通知": 0},
"priority_statistics": {"低": 2, "中": 4, "高": 3, "紧急": 1}
}
}
```
## 三、备注
- 列表与详情序列包含显示用枚举字段(如 `notice_type_display`, `priority_display`, `status_display`)。
- 用户视图中 `user_get_notice_detail` 会自动将未读标记为已读并累加 `view_count`。
- 管理员发布接口会为所有启用用户创建 `HertzUserNotice` 状态记录。

View File

@@ -0,0 +1,83 @@
# Hertz Studio Django 验证码模块接口文档
- 基础路径: `/api/captcha/`
- 统一响应: 使用 `HertzResponse`,结构如下(参考 `hertz_studio_django_utils/responses/HertzResponse.py`
```json
{
"success": true,
"code": 200,
"message": "操作成功",
"data": {}
}
```
- 路由挂载: 项目主路由中已通过 `path('api/captcha/', include('hertz_studio_django_captcha.urls'))` 挂载(`hertz_server_django/urls.py:28`)。
- 认证说明: 接口允许匿名访问(`AllowAny`),不需要登录(`hertz_studio_django_captcha/api_views.py:18`, `hertz_studio_django_captcha/api_views.py:60`)。
- 验证接口说明: 独立的验证码验证接口已删除,验证逻辑集成到具体业务接口(`hertz_studio_django_captcha/api_views.py:54`)。
## 一、生成验证码
### 生成新的验证码
- 方法: `POST`
- 路径: `/api/captcha/generate/`
- 认证: 不需要登录
- 实现: `CaptchaGenerateAPIView.post``hertz_studio_django_captcha/api_views.py:38`
- 请求示例:
```bash
curl -X POST "http://localhost:8000/api/captcha/generate/"
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "验证码生成成功",
"data": {
"captcha_id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890",
"image_data": "data:image/png;base64,iVBORw0KGgo...",
"expires_in": 300
}
}
```
## 二、刷新验证码
### 刷新已有验证码,生成新图像
- 方法: `POST`
- 路径: `/api/captcha/refresh/`
- 认证: 不需要登录
- 请求体: `application/json`
- 字段: `captcha_id` 旧验证码ID
- 实现: `CaptchaRefreshAPIView.post``hertz_studio_django_captcha/api_views.py:84`
- 请求示例:
```http
POST /api/captcha/refresh/
Content-Type: application/json
{"captcha_id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890"}
```
- 返回示例:
```json
{
"success": true,
"code": 200,
"message": "验证码刷新成功",
"data": {
"captcha_id": "f2b3c4d5-e6f7-8901-bcde-0123456789ab",
"image_data": "data:image/png;base64,iVBORw0KGgo...",
"expires_in": 300
}
}
```
## 三、错误响应示例
- 参数不完整(刷新接口):
```json
{"success": false, "code": 400, "message": "参数不完整"}
```
- 生成/刷新失败(异常信息):
```json
{"success": false, "code": 500, "message": "验证码生成失败", "error": "<错误信息>"}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
docs/img/img_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
docs/img/img_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
docs/img/img_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

68
docs/使用手册.md Normal file
View File

@@ -0,0 +1,68 @@
# 使用手册
## 一、**环境要求**
- `Python 3.10+`(建议 3.11/3.12
- 操作系统WindowsPowerShell
- 可选:本地 `Redis` 服务(默认地址 `redis://127.0.0.1:6379`
## 二、**依赖安装**
- 一次性使用镜像安装:
在项目根目录终端执行:
`pip install -r requirements.txt -i https://hertz:hertz@hzpypi.hzsystems.cn/simple/`
- 注:若提示依赖安装失败,请将机器码保存并联系管理员开通机器码注册
## **三、启动服务**
- 通过脚本启动(支持端口参数):
- `python start_server.py --port 8000`
- 访问地址:
- `http://127.0.0.1:8000/`
- WebSocket`ws://127.0.0.1:8000/ws/`
- 首次启动将自动执行:
- 扫描并注册新应用到 `INSTALLED_APPS``urls.py``start_server.py:173``start_server.py:98`)。
- 执行 `makemigrations``migrate``start_server.py:1109`)。
- 初始化超级管理员/部门/菜单/角色等(`start_server.py:877`)。
- 创建菜单生成器工具 `generate_menu.py``start_server.py:780`)。
- 启动 `daphne` 并开启热重启监听(`start_server.py:1016``start_server.py:1063`)。
## 四、默认账号
- 超级管理员:
- 用户名:`hertz`
- 密码:`hertz`
- 普通用户
- 用户名:`demo`
- 密码:`123456`
## 五、**常见配置说明**
- CORS通过 `.env` 配置 `CORS_ALLOWED_ORIGINS``CORS_ALLOW_ALL_ORIGINS`
- 静态与媒体:
- 静态:`STATICFILES_DIRS = [BASE_DIR / 'static']`
- 媒体:`MEDIA_ROOT = BASE_DIR / 'media'``hertz_server_django/settings.py:209-216`)。
- WebSocket使用 `channels``channels-redis`,层配置读取 `REDIS_URL``hertz_server_django/settings.py:302-309`)。
## **六、问题排查**
- `daphne``watchdog` 未安装:
- 运行:`pip install daphne watchdog -i https://pypi.tuna.tsinghua.edu.cn/simple``start_server.py:1235-1248` 有依赖检查)。
- Redis 未运行:
- 安装并启动 Redis或调整 `REDIS_URL` 指向可用实例。
## **七、项目结构**
- 核心配置:`hertz_server_django/settings.py``hertz_server_django/urls.py`
- 启动脚本:`start_server.py`
- 依赖清单:`requirements.txt`
- 静态资源:`static/`,媒体资源:`media/`
- 业务模块:`hertz_studio_django_*` 系列包鉴权、日志、通知、监控、Wiki、AI、YOLO、代码生成等
## 八、**快速启动**
- 安装依赖:`pip install -r requirements.txt -i https://hertz:hertz@hzpypi.hzsystems.cn/simple/`
- 启动服务:`python start_server.py --port 8000`

158
docs/开发规范.md Normal file
View File

@@ -0,0 +1,158 @@
# 新功能开发规范
## 一、命名规范
- APP 名称:`hertz_studio_django_xxx`,全小写,用下划线分隔
- Python 包与模块:全小写,短名称,避免缩写不清晰
- URL 命名空间:`app_name = 'hertz_studio_django_xxx'`
- 数据库表名:`Meta.db_table = 'hertz_xxx_model'`,避免与其他库冲突
- 迁移文件命名:描述性动词+对象,如 `0003_add_field_to_model`
## 二、项目结构与约定
- 标准结构:`apps.py``models.py``serializers.py``views.py``urls.py``admin.py`
- 静态资源:`media/<app_name>/...` 放置同路径资源以覆盖库静态文件
- 配置集中:在 `settings.py` 维护,使用前缀化大写变量(如 `YOLO_MODEL`
## 三、接口返回规范
- 统一使用 `HertzResponse`(路径:`hertz_studio_django_utils/responses/HertzResponse.py`
- 成功:
```python
from hertz_studio_django_utils.responses.HertzResponse import HertzResponse
return HertzResponse.success(data={'id': 1}, message='操作成功')
```
- 失败:
```python
return HertzResponse.fail(message='业务失败')
```
- 错误:
```python
return HertzResponse.error(message='系统错误', error=str(e))
```
- 验证失败:
```python
return HertzResponse.validation_error(message='参数验证失败', errors=serializer.errors)
```
- 统一键:`success | code | message | data`,禁止返回非标准顶层结构
## 四、API 设计规范
- 路径语义化:`/models/`、`/detections/`、`/alerts/`
- 方法约定:`GET` 查询、`POST` 创建/动作、`PUT/PATCH` 更新、`DELETE` 删除
- 分页:请求参数 `page, page_size`;响应 `total, items`
- 过滤与排序:查询参数 `q, order_by, filters`;谨慎开放可排序字段
## 五、认证与授权
- 强制认证:业务敏感接口使用登录态(装饰器或 DRF 权限类)
- 权限控制:按用户/组/角色配置;避免在视图中硬编码权限
- 速率限制:对登录、验证码、检测等接口进行限流
## 六、日志与审计
- 请求审计:记录请求方法、路径、用户、响应码、耗时
- 业务事件:模型启用/删除、检测结果、告警变更记录
- 脱敏:对密码、令牌、隐私字段进行统一脱敏
## 七、配置约定
- 所有库配置集中在 `settings.py`,使用前缀化变量:
- AI`AI_MODEL_PROVIDER`、`AI_DEFAULT_MODEL`、`AI_TIMEOUT`
- Auth`AUTH_LOGIN_REDIRECT_URL`、`AUTH_ENABLE_OAUTH`
- Captcha`CAPTCHA_TYPE`、`CAPTCHA_EXPIRE_SECONDS`
- Log`LOG_LEVEL`、`LOG_SINKS`、`LOG_REDACT_FIELDS`
- Notice`NOTICE_CHANNELS`、`NOTICE_RETRY`
- Monitor`MONITOR_PROBES`、`MONITOR_ALERTS`
- Wiki`WIKI_MARKDOWN`、`WIKI_SEARCH_BACKEND`
- YOLO`YOLO_MODEL`、`YOLO_DEVICE`、`YOLO_CONF_THRESHOLD`
- Codegen`CODEGEN_TEMPLATES_DIR`、`CODEGEN_OUTPUT_DIR`
## 八、可扩展性(不改库源码)
- 视图子类化 + 路由覆盖:在项目中继承库视图并替换路由匹配
```python
# urls.py
from django.urls import path, include
from your_app.views import MyDetectView
urlpatterns = [
path('yolo/detect/', MyDetectView.as_view(), name='detect'),
path('yolo/', include('hertz_studio_django_yolo.urls', namespace='hertz_studio_django_yolo')),
]
```
- 猴子补丁:在 `AppConfig.ready()` 将库函数替换为自定义函数
```python
# apps.py
from django.apps import AppConfig
class YourAppConfig(AppConfig):
name = 'your_app'
def ready(self):
from hertz_studio_django_yolo import views as yviews
from your_app.views import my_yolo_detection
yviews.yolo_detection = my_yolo_detection
```
- Admin 重注册:`unregister` 后 `register` 自定义 `ModelAdmin`
- 信号连接:在 `ready()` 中连接库暴露的信号以扩展行为
## 九、示例:覆写 YOLO 检测返回值
- 目标位置:`hertz_studio_django_yolo/views.py:586-603`
- 最小替换示例(路由覆盖):
```python
from rest_framework.decorators import api_view, parser_classes
from rest_framework.parsers import MultiPartParser, FormParser
from django.contrib.auth.decorators import login_required
from hertz_studio_django_utils.responses.HertzResponse import HertzResponse
from hertz_studio_django_yolo.views import _perform_detection
from hertz_studio_django_yolo.models import YoloModel, DetectionRecord
import uuid, os, time, shutil
@api_view(['POST'])
@parser_classes([MultiPartParser, FormParser])
@login_required
def my_yolo_detection(request):
# 复用库的流程,省略若干步骤,仅演示返回体差异化
serializer = hertz_studio_django_yolo.serializers.DetectionRequestSerializer(data=request.data)
if not serializer.is_valid():
return HertzResponse.validation_error(message='参数验证失败', errors=serializer.errors)
uploaded_file = serializer.validated_data['file']
yolo_model = YoloModel.get_enabled_model()
original_path = '...' # 省略:存储原始文件
result_path, object_count, detected_categories, confidence_scores, avg_confidence = _perform_detection('...', yolo_model.model_path, 0.5, 'image', yolo_model)
processing_time = time.time() - time.time()
detection_record = DetectionRecord.objects.create(
original_file=original_path,
result_file='...',
detection_type='image',
model_name=f"{yolo_model.name} {yolo_model.version}",
model=yolo_model,
user=request.user,
user_name=request.user.username,
object_count=object_count,
detected_categories=detected_categories,
confidence_threshold=0.5,
confidence_scores=confidence_scores,
avg_confidence=avg_confidence,
processing_time=processing_time
)
return HertzResponse.success(
data={
'id': detection_record.id,
'file': {
'result_url': detection_record.result_file.url,
'original_url': detection_record.original_file.url
},
'stats': {
'count': object_count,
'categories': detected_categories,
'scores': confidence_scores,
'avg_score': round(avg_confidence, 4) if avg_confidence is not None else None,
'time': round(processing_time, 2)
},
'model': {
'name': yolo_model.name,
'version': yolo_model.version,
'threshold': 0.5
},
'user': {
'id': getattr(request.user, 'user_id', None),
'name': request.user.username
}
},
message='检测完成'
)
```

53
docs/项目简介.md Normal file
View File

@@ -0,0 +1,53 @@
# Hertz Studio 后端
## **一、系统简介**
- 统一后端服务,提供 `REST API``WebSocket`,面向 AI 工作室与通用后台场景。
- 模块化设计覆盖认证与权限、通知公告、日志、知识库、系统监控、AI 对话、代码生成、Sklearn 推理、YOLO 目标检测等。
- 基于 `ASGI` 架构,使用 `Daphne` 运行;默认使用 `SQLite`,可切换 `MySQL`;缓存与消息通道使用 `Redis`
- 自动化启动脚本 `start_server.py` 支持数据库迁移与初始数据(菜单、角色、超级管理员)初始化,以及热重启文件监听。
## 二、**体验账户**
- 管理员
账号hertz 密码hertz
- 普通用户
账号demo 密码123456
## 三、**技术栈**
- 后端框架:`Django 5``Django REST Framework``Channels` + `Daphne`
- 数据与缓存:`SQLite`(默认)/ `MySQL`(可选)、`Redis`缓存、会话、Channel Layer
- API 文档:`drf-spectacular` 自动生成,提供 Swagger 与 Redoc 页面。
- 认证与安全:自定义 `AuthMiddleware` + `JWT``pyjwt``CORS` 支持。
- AI / ML`Ultralytics YOLO``OpenCV``NumPy``Scikit-learn``Joblib`,以及本地 `Ollama` 对话集成。
- 工具与其他:`Mako` 模板(代码生成)、`Pillow``watchdog``psutil``GPUtil`
## **四、功能**
- 认证与权限(`hertz_studio_django_auth`
- 用户注册/登录/登出、密码管理、用户信息维护。
- `JWT` 发放与刷新,角色/菜单权限体系,接口权限由 `AuthMiddleware` 统一控制。
- 图形验证码(`hertz_studio_django_captcha`
- 可配置验证码生成与校验、尺寸/颜色/噪声等参数支持。
- 通知公告(`hertz_studio_django_notice`
- 公告 CRUD、状态管理面向工作室信息发布。
- 日志管理(`hertz_studio_django_log`
- 操作日志采集与查询,支持接口级日志记录装饰器。
- 知识库(`hertz_studio_django_wiki`
- 文章与分类管理,面向知识内容沉淀与检索。
- 系统监控(`hertz_studio_django_system_monitor`
- CPU/内存/磁盘/GPU 指标采集与展示,基于 `psutil`/`GPUtil`
- AI 对话(`hertz_studio_django_ai`
- 对接本地 `Ollama`,提供对话接口与 `WebSocket` 推送能力。
- 代码生成(`hertz_studio_django_codegen`
- 基于 `Mako` 的 Django 代码与菜单生成器,支持 CLI 生成并同步权限。
- Sklearn/PyTorch 推理(`hertz_studio_django_sklearn`
- 模型上传、元数据解析(特征/输入输出模式)、统一预测接口,支持 `predict_proba`
- YOLO 目标检测(`hertz_studio_django_yolo`
- 模型管理与启用切换、检测接口、结果中文别名标注与图像绘制;默认模型位于 `static/models/yolov12/weights/best.pt`
- Demo 与首页(`hertz_demo`
- 示例页面验证码、邮件、WebSocket与首页模板 `templates/index.html`

84
generate_menu.py Normal file
View File

@@ -0,0 +1,84 @@
#!/usr/bin/env python
"""
菜单生成器命令行工具
用于快速生成菜单配置和权限同步
"""
import os
import sys
import argparse
import django
from pathlib import Path
# 添加项目路径
project_root = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, project_root)
# 设置Django环境
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hertz_server_django.settings')
django.setup()
from hertz_studio_django_utils.code_generator.menu_generator import MenuGenerator
def generate_crud_menu(args):
"""生成CRUD菜单"""
generator = MenuGenerator()
operations = args.operations.split(',') if args.operations else ['list', 'create', 'update', 'delete']
menus = generator.generate_menu_config(
module_name=args.module_name,
model_name=args.model_name,
operations=operations,
parent_code=args.parent_code,
menu_prefix=args.prefix,
sort_order=args.sort_order,
icon=args.icon
)
# 保存到待同步文件
pending_file = os.path.join(project_root, 'pending_menus.py')
with open(pending_file, 'w', encoding='utf-8') as f:
f.write('# 待同步的菜单配置\n')
f.write('pending_menus = [\n')
for menu in menus:
f.write(' {\n')
for key, value in menu.items():
if isinstance(value, str):
f.write(f" '{key}': '{value}',\n")
elif value is None:
f.write(f" '{key}': None,\n")
else:
f.write(f" '{key}': {value},\n")
f.write(' },\n')
f.write(']\n')
print(f"已生成 {len(menus)} 个菜单配置,保存到 pending_menus.py")
print("请重启服务器以同步菜单到数据库")
def menu_generator_main():
parser = argparse.ArgumentParser(description='菜单生成器')
subparsers = parser.add_subparsers(dest='command', help='可用命令')
# CRUD菜单生成命令
crud_parser = subparsers.add_parser('crud', help='生成CRUD菜单')
crud_parser.add_argument('module_name', help='模块名称(中文)')
crud_parser.add_argument('model_name', help='模型名称(英文)')
crud_parser.add_argument('--parent-code', default='system', help='父级菜单代码')
crud_parser.add_argument('--prefix', default='system', help='菜单前缀')
crud_parser.add_argument('--operations', help='操作列表(逗号分隔)')
crud_parser.add_argument('--sort-order', type=int, default=1, help='排序')
crud_parser.add_argument('--icon', help='图标')
args = parser.parse_args()
if args.command == 'crud':
generate_crud_menu(args)
else:
parser.print_help()
if __name__ == "__main__":
menu_generator_main()

13
get_machine_code.bat Normal file
View File

@@ -0,0 +1,13 @@
@echo off
chcp 65001 >nul
echo ================================
echo Hertz Django Get Machine Code
echo ================================
python get_machine_code.py
echo.
echo ================================
echo Please send the machine code to the after-sales personnel
echo ================================
pause >nul

18
get_machine_code.py Normal file
View File

@@ -0,0 +1,18 @@
import platform
import uuid
import hashlib
def get_machine_id() -> str:
"""生成机器码。
根据当前系统信息平台、架构、MAC地址生成唯一机器码
使用 SHA256 取前16位并转大写前缀为 HERTZ_STUDIO_。
"""
system_info = f"{platform.platform()}-{platform.machine()}-{uuid.getnode()}"
return 'HERTZ_STUDIO_' + hashlib.sha256(system_info.encode()).hexdigest()[:16].upper()
if __name__ == "__main__":
machine_code = get_machine_id()
print(f"您的机器码是: {machine_code}")

63
get_tokens.py Normal file
View File

@@ -0,0 +1,63 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
获取用户JWT token的脚本
"""
import os
import sys
import django
# 设置Django环境
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hertz_server_django.settings')
django.setup()
from hertz_studio_django_auth.models import HertzUser
from hertz_studio_django_auth.utils.auth.token_utils import TokenUtils
def get_user_tokens():
"""获取用户token"""
try:
# 获取普通用户hertz
user = HertzUser.objects.get(username='demo')
user_roles = user.roles.all()
print(f'找到用户: {user.username}, 角色: {[role.role_code for role in user_roles]}')
# 生成token
user_data = {
'user_id': str(user.user_id),
'username': user.username,
'email': user.email,
'roles': [role.role_code for role in user_roles],
'permissions': []
}
token_data = TokenUtils.generate_token(user_data)
print(f'普通用户token: {token_data}')
print()
# 获取管理员用户hertz
admin_user = HertzUser.objects.get(username='hertz')
admin_roles = admin_user.roles.all()
print(f'找到管理员: {admin_user.username}, 角色: {[role.role_code for role in admin_roles]}')
# 生成管理员token
admin_data = {
'user_id': str(admin_user.user_id),
'username': admin_user.username,
'email': admin_user.email,
'roles': [role.role_code for role in admin_roles],
'permissions': []
}
admin_token_data = TokenUtils.generate_token(admin_data)
print(f'管理员token: {admin_token_data}')
return {
'user_token': token_data,
'admin_token': admin_token_data
}
except Exception as e:
print(f'错误: {e}')
return None
if __name__ == '__main__':
get_user_tokens()

11
hertz.txt Normal file
View File

@@ -0,0 +1,11 @@
# ================hertz官方库================
hertz-studio-django-ai
hertz-studio-django-auth
hertz-studio-django-captcha
hertz-studio-django-kb
hertz-studio-django-log
hertz-studio-django-notice
hertz-studio-django-system-monitor
hertz-studio-django-wiki
hertz-studio-django-yolo
hertz-studio-django-yolo-train

274
hertz_demo/README.md Normal file
View File

@@ -0,0 +1,274 @@
# Hertz Demo 演示模块
## 📋 模块概述
Hertz Demo 模块是一个功能演示和测试模块,提供了完整的示例代码和交互式演示页面,帮助开发者快速了解和使用 Hertz Server Django 框架的各项功能特性。
## ✨ 功能特性
- **验证码演示**: 展示多种验证码类型的生成、刷新和验证功能
- **邮件系统演示**: 提供邮件模板预览和发送测试功能
- **WebSocket演示**: 实时通信功能演示和测试
- **交互式界面**: 美观的Web界面支持实时操作和反馈
- **完整示例代码**: 提供可直接参考的实现代码
## 📁 模块结构
```
hertz_demo/
├── __init__.py # 模块初始化
├── apps.py # Django应用配置
├── models.py # 数据模型(预留)
├── views.py # 视图函数和业务逻辑
├── urls.py # URL路由配置
├── tests.py # 单元测试
├── consumers.py # WebSocket消费者
├── routing.py # WebSocket路由
└── templates/ # 模板文件
├── captcha_demo.html # 验证码演示页面
├── email_demo.html # 邮件系统演示页面
└── websocket_demo.html # WebSocket演示页面
```
## 🎯 核心功能详解
### 1. 验证码演示功能
验证码演示页面提供三种验证码类型:
- **随机字符验证码**: 随机生成的字母数字组合
- **数学运算验证码**: 简单的数学计算验证
- **单词验证码**: 英文单词验证
**主要功能**:
- 验证码实时生成和刷新
- 前端Ajax验证
- 后端表单验证
- 验证码类型切换
### 2. 邮件系统演示功能
邮件演示页面提供多种邮件模板:
- **欢迎邮件**: 用户注册欢迎邮件模板
- **系统通知**: 系统消息通知模板
- **邮箱验证**: 邮箱验证邮件模板
- **自定义邮件**: 支持自定义主题和内容
**主要功能**:
- 邮件模板实时预览
- 邮件发送测试
- 收件人邮箱验证
- 发送状态反馈
### 3. WebSocket演示功能
WebSocket演示页面提供实时通信功能
- **连接状态管理**: 显示WebSocket连接状态
- **消息发送接收**: 实时消息通信
- **广播功能**: 消息广播演示
- **错误处理**: 连接异常处理
## 🚀 API接口
### 演示页面路由
| 路由 | 方法 | 描述 |
|------|------|------|
| `/demo/captcha/` | GET | 验证码演示页面 |
| `/demo/email/` | GET | 邮件系统演示页面 |
| `/demo/websocket/` | GET | WebSocket演示页面 |
| `/websocket/test/` | GET | WebSocket测试页面 |
### Ajax接口
**验证码相关**:
- `POST /demo/captcha/` (Ajax): 验证码刷新和验证
- 请求体: `{"action": "refresh/verify", "captcha_id": "...", "user_input": "..."}`
**邮件发送**:
- `POST /demo/email/` (Ajax): 发送演示邮件
- 请求体: 邮件类型、收件人邮箱、自定义内容等
## ⚙️ 配置参数
### 邮件配置settings.py
```python
# 邮件服务器配置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'your-email@gmail.com'
EMAIL_HOST_PASSWORD = 'your-app-password'
DEFAULT_FROM_EMAIL = 'noreply@yourdomain.com'
```
### WebSocket配置
```python
# ASGI配置
ASGI_APPLICATION = 'hertz_server_django.asgi.application'
# Channel layers配置
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
'hosts': [('127.0.0.1', 6379)],
},
},
}
```
## 🛠️ 快速开始
### 1. 访问演示页面
启动开发服务器后访问以下URL
```bash
# 验证码演示
http://localhost:8000/demo/captcha/
# 邮件系统演示
http://localhost:8000/demo/email/
# WebSocket演示
http://localhost:8000/demo/websocket/
```
### 2. 测试验证码功能
1. 打开验证码演示页面
2. 选择验证码类型
3. 点击验证码图片可刷新
4. 输入验证码进行验证
5. 观察验证结果反馈
### 3. 测试邮件功能
1. 打开邮件演示页面
2. 选择邮件模板类型
3. 输入收件人邮箱
4. 点击发送测试邮件
5. 查看发送状态
### 4. 测试WebSocket功能
1. 打开WebSocket演示页面
2. 点击"连接"按钮建立连接
3. 在输入框中发送消息
4. 观察消息接收和广播
5. 测试断开重连功能
## 🔧 高级用法
### 自定义邮件模板
`views.py` 中的 `generate_email_content` 函数中添加新的邮件模板:
```python
def generate_email_content(email_type, recipient_name, custom_subject='', custom_message=''):
email_templates = {
'your_template': {
'subject': '您的邮件主题',
'html_template': '''
<html>
<!-- 您的HTML模板内容 -->
</html>
'''
}
}
# ...
```
### 扩展验证码类型
在验证码演示中扩展新的验证码类型:
```python
# 在 captcha_demo 函数中添加新的验证码类型
captcha_types = {
'random_char': '随机字符验证码',
'math': '数学运算验证码',
'word': '单词验证码',
'new_type': '您的新验证码类型' # 新增类型
}
```
### WebSocket消息处理
`consumers.py` 中扩展WebSocket消息处理逻辑
```python
class DemoConsumer(WebsocketConsumer):
async def receive(self, text_data):
data = json.loads(text_data)
message_type = data.get('type')
if message_type == 'custom_message':
# 处理自定义消息类型
await self.handle_custom_message(data)
```
## 🧪 测试
### 运行单元测试
```bash
python manage.py test hertz_demo
```
### 测试覆盖范围
- 验证码功能测试
- 邮件发送测试
- WebSocket连接测试
- 页面渲染测试
## 🔒 安全考虑
### 验证码安全
- 验证码有效期限制
- 验证次数限制
- 防止暴力破解
### 邮件安全
- 收件人邮箱验证
- 发送频率限制
- 防止邮件滥用
### WebSocket安全
- 连接认证
- 消息内容过滤
- 防止DDoS攻击
## ❓ 常见问题
### Q: 邮件发送失败怎么办?
A: 检查邮件服务器配置确保SMTP设置正确邮箱密码为应用专用密码。
### Q: WebSocket连接失败怎么办
A: 检查Redis服务是否运行确保CHANNEL_LAYERS配置正确。
### Q: 验证码验证总是失败?
A: 检查验证码存储后端Redis是否正常运行。
### Q: 如何添加新的演示功能?
A: 在views.py中添加新的视图函数在urls.py中配置路由在templates中添加模板文件。
## 📝 更新日志
### v1.0.0 (2024-01-01)
- 初始版本发布
- 包含验证码、邮件、WebSocket演示功能
- 提供完整的示例代码和文档
## 🔗 相关链接
- [🏠 返回主项目](../README.md) - Hertz Server Django 主项目文档
- [🔐 认证授权模块](../hertz_studio_django_auth/README.md) - 用户管理和权限控制
- [🛠️ 工具类模块](../hertz_studio_django_utils/README.md) - 加密、邮件和验证工具
- [📋 代码风格指南](../docs/CODING_STYLE.md) - 开发规范和最佳实践
---
💡 **提示**: 此模块主要用于功能演示和学习参考,生产环境请根据实际需求进行适当调整和优化。

0
hertz_demo/__init__.py Normal file
View File

6
hertz_demo/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class DemoConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'hertz_demo'

120
hertz_demo/consumers.py Normal file
View File

@@ -0,0 +1,120 @@
import json
from datetime import datetime
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = f'chat_{self.room_name}'
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message_type = text_data_json.get('type', 'chat_message')
message = text_data_json.get('message', '')
username = text_data_json.get('username', 'Anonymous')
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': message_type,
'message': message,
'username': username,
'timestamp': datetime.now().strftime('%H:%M:%S')
}
)
# Receive message from room group
async def chat_message(self, event):
message = event['message']
username = event['username']
timestamp = event['timestamp']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'type': 'chat_message',
'message': message,
'username': username,
'timestamp': timestamp
}))
async def user_join(self, event):
username = event['username']
timestamp = event['timestamp']
await self.send(text_data=json.dumps({
'type': 'user_notification',
'message': f'{username} 加入了聊天室',
'username': username,
'timestamp': timestamp
}))
async def user_leave(self, event):
username = event['username']
timestamp = event['timestamp']
await self.send(text_data=json.dumps({
'type': 'user_notification',
'message': f'{username} 离开了聊天室',
'username': username,
'timestamp': timestamp
}))
class EchoConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.accept()
async def disconnect(self, close_code):
pass
async def receive(self, text_data):
try:
# 解析接收到的JSON数据
data = json.loads(text_data)
message = data.get('message', '').strip()
if message:
# 返回回声消息
response = {
'type': 'echo_message',
'original_message': message,
'echo_message': f'回声: {message}',
'timestamp': datetime.now().strftime('%H:%M:%S')
}
else:
# 如果消息为空
response = {
'type': 'error',
'message': '消息不能为空',
'timestamp': datetime.now().strftime('%H:%M:%S')
}
await self.send(text_data=json.dumps(response, ensure_ascii=False))
except json.JSONDecodeError:
# JSON解析错误
error_response = {
'type': 'error',
'message': '无效的JSON格式',
'timestamp': datetime.now().strftime('%H:%M:%S')
}
await self.send(text_data=json.dumps(error_response, ensure_ascii=False))

View File

3
hertz_demo/models.py Normal file
View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

7
hertz_demo/routing.py Normal file
View File

@@ -0,0 +1,7 @@
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
re_path(r'ws/echo/$', consumers.EchoConsumer.as_asgi()),
]

View File

@@ -0,0 +1,499 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hertz验证码演示 - Hertz Server Django</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 2rem;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 2rem;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
}
.demo-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.demo-card {
background: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
overflow: hidden;
}
.demo-header {
background: #667eea;
color: white;
padding: 1rem;
text-align: center;
}
.demo-header h3 {
font-size: 1.3rem;
margin-bottom: 0.5rem;
}
.demo-content {
padding: 1.5rem;
}
@media (max-width: 768px) {
.demo-grid {
grid-template-columns: 1fr;
}
.header h1 {
font-size: 2rem;
}
body {
padding: 1rem;
}
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #333;
}
.form-group input {
width: 100%;
padding: 0.8rem;
border: 2px solid #e1e5e9;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.3s ease;
}
.form-group input:focus {
outline: none;
border-color: #667eea;
}
.captcha-container {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.captcha-image {
border: 2px solid #e1e5e9;
border-radius: 8px;
cursor: pointer;
transition: border-color 0.3s ease;
}
.captcha-image:hover {
border-color: #667eea;
}
.btn {
padding: 0.8rem 1.5rem;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
transition: background 0.3s ease;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5a6fd8;
}
.btn-success {
background: #4CAF50;
color: white;
}
.btn-success:hover {
background: #45a049;
}
.btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.refresh-btn {
background: #667eea;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 5px;
cursor: pointer;
font-size: 0.9rem;
transition: background 0.3s ease;
}
.refresh-btn:hover {
background: #5a6fd8;
}
.submit-btn {
width: 100%;
background: #667eea;
color: white;
border: none;
padding: 1rem;
border-radius: 5px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: background 0.3s ease;
}
.submit-btn:hover {
background: #5a6fd8;
}
.back-link {
text-align: center;
margin-top: 2rem;
}
.back-link a {
color: white;
text-decoration: none;
font-weight: 500;
font-size: 1.1rem;
}
.back-link a:hover {
text-decoration: underline;
}
.info-box {
background: #f8f9fa;
border-left: 4px solid #667eea;
padding: 1rem;
margin-bottom: 2rem;
border-radius: 0 8px 8px 0;
}
.info-box h3 {
color: #667eea;
margin-bottom: 0.5rem;
}
.info-box ul {
margin-left: 1.5rem;
color: #666;
}
.info-box li {
margin-bottom: 0.3rem;
}
.info-box code {
background: #e9ecef;
padding: 0.2rem 0.4rem;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
}
.captcha-types {
background: #f8f9fa;
border-left: 4px solid #28a745;
padding: 1rem;
margin-bottom: 2rem;
border-radius: 0 8px 8px 0;
}
.type-buttons {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
margin-bottom: 0.5rem;
}
.type-btn {
background: #e9ecef;
color: #495057;
border: 2px solid #dee2e6;
padding: 0.5rem 1rem;
border-radius: 5px;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.3s ease;
flex: 1;
min-width: 120px;
}
.type-btn:hover {
background: #dee2e6;
border-color: #adb5bd;
}
.type-btn.active {
background: #28a745;
color: white;
border-color: #28a745;
}
.demo-section {
background: #fff3cd;
border-left: 4px solid #ffc107;
padding: 1rem;
margin-bottom: 2rem;
border-radius: 0 8px 8px 0;
}
.demo-section h4 {
color: #856404;
margin-bottom: 0.5rem;
}
.demo-section p {
color: #856404;
margin-bottom: 0.3rem;
}
.success-message {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔐 Hertz验证码演示</h1>
<p>{{ demo_description }}</p>
</div>
<div class="demo-grid">
<!-- 验证码功能介绍 -->
<div class="demo-card">
<div class="demo-header">
<h3>🎯 Hertz验证码功能特性</h3>
<p>自定义验证码系统支持Redis缓存</p>
</div>
<div class="demo-content">
<div class="info-box">
<ul>
<li>🔤 随机字符验证码 - 生成随机字母数字组合</li>
<li>🎨 自定义样式配置 - 支持颜色、字体、噪声等设置</li>
<li>⚡ Ajax刷新功能 - 无需刷新页面</li>
<li>💾 Redis缓存 - 高性能数据存储</li>
<li>⏰ 超时自动失效 - 可配置过期时间</li>
<li>🔧 灵活配置 - 通过settings.py进行配置</li>
</ul>
</div>
<div class="captcha-types">
<h3 style="margin-bottom: 1rem; color: #667eea;">配置信息</h3>
<p style="color: #666; font-size: 0.9rem; margin-bottom: 0.5rem;">• 验证码长度: 可通过HERTZ_CAPTCHA_LENGTH配置</p>
<p style="color: #666; font-size: 0.9rem; margin-bottom: 0.5rem;">• 图片尺寸: 可通过HERTZ_CAPTCHA_WIDTH/HEIGHT配置</p>
<p style="color: #666; font-size: 0.9rem; margin-bottom: 0.5rem;">• 过期时间: 可通过HERTZ_CAPTCHA_TIMEOUT配置</p>
<p style="color: #666; font-size: 0.9rem;">• Redis前缀: 可通过HERTZ_CAPTCHA_REDIS_KEY_PREFIX配置</p>
</div>
</div>
</div>
<!-- 验证码测试 -->
<div class="demo-card">
<div class="demo-header">
<h3>🔒 Hertz验证码测试</h3>
<p>输入验证码进行功能测试</p>
</div>
<div class="demo-content">
{% if messages %}
{% for message in messages %}
<div class="success-message" style="{% if message.tags == 'error' %}background: #f8d7da; color: #721c24; border-color: #f5c6cb;{% endif %}">
{% if message.tags == 'success' %}✅{% elif message.tags == 'error' %}❌{% endif %} {{ message }}
</div>
{% endfor %}
{% endif %}
<div class="demo-section">
<h4>📋 Hertz验证码说明</h4>
<p>• 随机字符验证码:生成随机字母和数字组合</p>
<p>• 特点自定义样式支持噪声干扰Redis缓存存储</p>
<p>• 功能支持Ajax刷新自动过期失效</p>
</div>
<form method="post" id="captcha-form">
{% csrf_token %}
<div class="form-group">
<label for="captcha_input">验证码:</label>
<div class="captcha-container">
<img id="captcha-image" src="{{ initial_captcha.image_data }}"
alt="验证码" class="captcha-image" onclick="refreshCaptcha()"
style="cursor: pointer; border: 2px solid #e1e5e9; border-radius: 8px;">
<button type="button" class="refresh-btn" onclick="refreshCaptcha()">🔄 刷新</button>
</div>
<input type="text" id="captcha_input" name="captcha_input"
placeholder="请输入验证码" required
style="width: 100%; padding: 0.8rem; border: 2px solid #e1e5e9; border-radius: 8px; font-size: 1rem; margin-top: 0.5rem;">
<input type="hidden" id="captcha_id" name="captcha_id" value="{{ initial_captcha.captcha_id }}">
</div>
<button type="submit" class="submit-btn">验证提交</button>
</form>
<div style="margin-top: 1rem; padding: 1rem; background: #e7f3ff; border-radius: 8px; border-left: 4px solid #007bff;">
<h4 style="color: #007bff; margin-bottom: 0.5rem;">💡 使用提示</h4>
<p style="color: #004085; margin-bottom: 0.3rem;">• 点击验证码图片可以刷新</p>
<p style="color: #004085; margin-bottom: 0.3rem;">• 验证码不区分大小写</p>
<p style="color: #004085;">• 验证码有效期为5分钟</p>
</div>
</div>
</div>
</div>
<div class="back-link">
<a href="/" style="color: white; text-decoration: none; font-weight: 500; font-size: 1.1rem;">← 返回首页</a>
</div>
</div>
<script>
let currentCaptchaId = '{{ initial_captcha.captcha_id }}';
function refreshCaptcha() {
fetch(window.location.href, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
},
body: JSON.stringify({
action: 'refresh',
captcha_id: currentCaptchaId
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('captcha-image').src = data.data.image_data;
document.getElementById('captcha_id').value = data.data.captcha_id;
document.getElementById('captcha_input').value = '';
currentCaptchaId = data.data.captcha_id;
} else {
console.error('刷新验证码失败:', data.error);
alert('刷新验证码失败,请重试');
}
})
.catch(error => {
console.error('刷新验证码失败:', error);
alert('刷新验证码失败,请重试');
});
}
// 表单提交处理
const captchaForm = document.getElementById('captcha-form');
if (captchaForm) {
captchaForm.addEventListener('submit', function(e) {
e.preventDefault();
const captchaId = document.getElementById('captcha_id').value;
const userInput = document.getElementById('captcha_input').value;
if (!userInput.trim()) {
alert('请输入验证码');
return;
}
fetch(window.location.href, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
},
body: JSON.stringify({
action: 'verify',
captcha_id: captchaId,
user_input: userInput
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
if (data.valid) {
alert('✅ ' + data.message);
document.getElementById('captcha_input').value = '';
refreshCaptcha();
} else {
alert('❌ ' + data.message);
document.getElementById('captcha_input').value = '';
refreshCaptcha();
}
} else {
alert('❌ 验证失败: ' + (data.error || '未知错误'));
refreshCaptcha();
}
})
.catch(error => {
console.error('提交失败:', error);
alert('❌ 提交失败,请重试');
refreshCaptcha();
});
});
}
// 回车键提交
document.getElementById('captcha_input').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
captchaForm.dispatchEvent(new Event('submit'));
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,520 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>邮件系统演示 - Hertz Server Django</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 2rem;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 2rem;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
}
.back-link {
margin-bottom: 1rem;
}
.back-link a {
color: white;
text-decoration: none;
font-size: 1.1rem;
opacity: 0.9;
transition: opacity 0.3s ease;
}
.back-link a:hover {
opacity: 1;
text-decoration: underline;
}
.demo-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.demo-card {
background: white;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.demo-card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 40px rgba(0,0,0,0.15);
}
.demo-card h3 {
color: #667eea;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.demo-card p {
color: #666;
margin-bottom: 1rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #333;
}
.form-group input,
.form-group textarea,
.form-group select {
width: 100%;
padding: 0.8rem;
border: 2px solid #e1e5e9;
border-radius: 5px;
font-size: 1rem;
transition: border-color 0.3s ease;
}
.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
outline: none;
border-color: #667eea;
}
.form-group textarea {
resize: vertical;
min-height: 120px;
}
.btn {
display: inline-block;
padding: 0.8rem 1.5rem;
border: none;
border-radius: 5px;
font-size: 1rem;
font-weight: 500;
text-decoration: none;
cursor: pointer;
transition: all 0.3s ease;
margin-right: 0.5rem;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5a6fd8;
transform: translateY(-2px);
}
.btn-success {
background: #28a745;
color: white;
}
.btn-success:hover {
background: #218838;
transform: translateY(-2px);
}
.btn:disabled {
background: #6c757d;
cursor: not-allowed;
transform: none;
}
.alert {
padding: 1rem;
border-radius: 5px;
margin-bottom: 1rem;
font-weight: 500;
}
.alert-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.alert-info {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.email-types {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.type-btn {
padding: 0.5rem 1rem;
border: 2px solid #667eea;
background: white;
color: #667eea;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.9rem;
}
.type-btn.active,
.type-btn:hover {
background: #667eea;
color: white;
}
.email-preview {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 5px;
padding: 1rem;
margin-top: 1rem;
}
.email-preview h4 {
color: #495057;
margin-bottom: 0.5rem;
}
.email-preview .preview-content {
background: white;
padding: 1rem;
border-radius: 3px;
border-left: 4px solid #667eea;
}
.loading {
display: none;
text-align: center;
padding: 1rem;
}
.loading.show {
display: block;
}
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 0 auto 0.5rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@media (max-width: 768px) {
.demo-grid {
grid-template-columns: 1fr;
}
.header h1 {
font-size: 2rem;
}
body {
padding: 1rem;
}
.email-types {
justify-content: center;
}
}
</style>
</head>
<body>
<div class="container">
<div class="back-link">
<a href="/">← 返回首页</a>
</div>
<div class="header">
<h1>📧 邮件系统演示</h1>
<p>体验Django邮件发送功能支持多种邮件类型和模板</p>
</div>
<div class="demo-grid">
<div class="demo-card">
<h3>📮 邮件发送功能</h3>
<p>本演示展示了Django邮件系统的核心功能</p>
<ul style="color: #666; margin-left: 1.5rem; margin-bottom: 1rem;">
<li>支持HTML和纯文本邮件</li>
<li>多种邮件模板类型</li>
<li>SMTP配置和发送状态</li>
<li>邮件预览和验证</li>
</ul>
<div class="email-types">
<button class="type-btn active" data-type="welcome">欢迎邮件</button>
<button class="type-btn" data-type="notification">通知邮件</button>
<button class="type-btn" data-type="verification">验证邮件</button>
<button class="type-btn" data-type="custom">自定义邮件</button>
</div>
</div>
<div class="demo-card">
<h3>✉️ 发送邮件测试</h3>
<div id="message-area"></div>
<form id="email-form" method="post">
{% csrf_token %}
<input type="hidden" id="email-type" name="email_type" value="welcome">
<div class="form-group">
<label for="recipient_email">收件人邮箱 *</label>
<input type="email" id="recipient_email" name="recipient_email" required
placeholder="请输入收件人邮箱地址">
</div>
<div class="form-group" id="subject-group">
<label for="subject">邮件主题</label>
<input type="text" id="subject" name="subject"
placeholder="邮件主题将根据类型自动生成">
</div>
<div class="form-group" id="message-group" style="display: none;">
<label for="message">邮件内容</label>
<textarea id="message" name="message"
placeholder="请输入自定义邮件内容"></textarea>
</div>
<div class="form-group">
<label for="recipient_name">收件人姓名</label>
<input type="text" id="recipient_name" name="recipient_name"
placeholder="收件人姓名(可选)">
</div>
<button type="submit" class="btn btn-primary" id="send-btn">
📤 发送邮件
</button>
<button type="button" class="btn btn-success" id="preview-btn">
👁️ 预览邮件
</button>
</form>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>正在发送邮件,请稍候...</p>
</div>
</div>
</div>
<div class="demo-card" id="preview-card" style="display: none;">
<h3>📋 邮件预览</h3>
<div class="email-preview">
<h4>邮件内容预览:</h4>
<div class="preview-content" id="preview-content">
<!-- 预览内容将在这里显示 -->
</div>
</div>
</div>
</div>
<script>
// 邮件类型切换
document.querySelectorAll('.type-btn').forEach(btn => {
btn.addEventListener('click', function() {
// 移除所有active类
document.querySelectorAll('.type-btn').forEach(b => b.classList.remove('active'));
// 添加active类到当前按钮
this.classList.add('active');
const type = this.dataset.type;
document.getElementById('email-type').value = type;
// 根据类型显示/隐藏字段
const subjectGroup = document.getElementById('subject-group');
const messageGroup = document.getElementById('message-group');
const subjectInput = document.getElementById('subject');
if (type === 'custom') {
messageGroup.style.display = 'block';
subjectInput.placeholder = '请输入邮件主题';
subjectInput.required = true;
} else {
messageGroup.style.display = 'none';
subjectInput.placeholder = '邮件主题将根据类型自动生成';
subjectInput.required = false;
}
// 更新预览
updatePreview();
});
});
// 表单提交
document.getElementById('email-form').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const sendBtn = document.getElementById('send-btn');
const loading = document.getElementById('loading');
const messageArea = document.getElementById('message-area');
// 显示加载状态
sendBtn.disabled = true;
loading.classList.add('show');
messageArea.innerHTML = '';
fetch('/demo/email/', {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
loading.classList.remove('show');
sendBtn.disabled = false;
if (data.success) {
messageArea.innerHTML = `
<div class="alert alert-success">
${data.message}
</div>
`;
} else {
messageArea.innerHTML = `
<div class="alert alert-error">
${data.message}
</div>
`;
}
})
.catch(error => {
loading.classList.remove('show');
sendBtn.disabled = false;
messageArea.innerHTML = `
<div class="alert alert-error">
❌ 发送失败:网络错误
</div>
`;
});
});
// 预览功能
document.getElementById('preview-btn').addEventListener('click', function() {
updatePreview();
const previewCard = document.getElementById('preview-card');
previewCard.style.display = previewCard.style.display === 'none' ? 'block' : 'none';
});
function updatePreview() {
const type = document.getElementById('email-type').value;
const recipientName = document.getElementById('recipient_name').value || '用户';
const subject = document.getElementById('subject').value;
const message = document.getElementById('message').value;
let previewContent = '';
switch(type) {
case 'welcome':
previewContent = `
<h4>🎉 欢迎加入我们!</h4>
<p>亲爱的 ${recipientName}</p>
<p>欢迎您注册成为我们的用户!我们很高兴您能加入我们的大家庭。</p>
<p>在这里,您可以享受到优质的服务和丰富的功能。如果您有任何问题,请随时联系我们。</p>
<p>祝您使用愉快!</p>
<p>此致<br>Hertz Server Django 团队</p>
`;
break;
case 'notification':
previewContent = `
<h4>🔔 系统通知</h4>
<p>亲爱的 ${recipientName}</p>
<p>您有一条新的系统通知:</p>
<div style="background: #f8f9fa; padding: 1rem; border-left: 4px solid #007bff; margin: 1rem 0;">
<p>您的账户设置已更新,如果这不是您的操作,请立即联系我们。</p>
</div>
<p>如有疑问,请联系客服。</p>
<p>此致<br>Hertz Server Django 团队</p>
`;
break;
case 'verification':
previewContent = `
<h4>🔐 邮箱验证</h4>
<p>亲爱的 ${recipientName}</p>
<p>请点击下面的链接验证您的邮箱地址:</p>
<div style="text-align: center; margin: 2rem 0;">
<a href="#" style="background: #667eea; color: white; padding: 1rem 2rem; text-decoration: none; border-radius: 5px;">验证邮箱</a>
</div>
<p>如果您没有注册账户,请忽略此邮件。</p>
<p>此致<br>Hertz Server Django 团队</p>
`;
break;
case 'custom':
previewContent = `
<h4>${subject || '自定义邮件'}</h4>
<p>亲爱的 ${recipientName}</p>
<div style="margin: 1rem 0;">
${message ? message.replace(/\n/g, '<br>') : '请输入邮件内容'}
</div>
<p>此致<br>Hertz Server Django 团队</p>
`;
break;
}
document.getElementById('preview-content').innerHTML = previewContent;
}
// 初始化预览
updatePreview();
</script>
</body>
</html>

View File

@@ -0,0 +1,556 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket演示 - Hertz Server Django</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 2rem;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 2rem;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
}
.demo-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.demo-card {
background: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
overflow: hidden;
}
.demo-header {
background: #667eea;
color: white;
padding: 1rem;
text-align: center;
}
.demo-header h3 {
font-size: 1.3rem;
margin-bottom: 0.5rem;
}
.demo-content {
padding: 1.5rem;
}
.connection-status {
display: inline-block;
padding: 0.3rem 0.8rem;
border-radius: 20px;
font-size: 0.8rem;
font-weight: bold;
margin-bottom: 1rem;
}
.status-disconnected {
background: #f44336;
color: white;
}
.status-connecting {
background: #ff9800;
color: white;
}
.status-connected {
background: #4CAF50;
color: white;
}
.message-area {
border: 1px solid #ddd;
border-radius: 5px;
height: 300px;
overflow-y: auto;
padding: 1rem;
margin-bottom: 1rem;
background: #f9f9f9;
font-family: monospace;
font-size: 0.9rem;
}
.message {
margin-bottom: 0.5rem;
padding: 0.3rem;
border-radius: 3px;
}
.message-system {
background: #e3f2fd;
color: #1976d2;
}
.message-user {
background: #e8f5e8;
color: #2e7d32;
}
.message-echo {
background: #fff3e0;
color: #f57c00;
}
.message-error {
background: #ffebee;
color: #c62828;
}
.message-notification {
background: #f3e5f5;
color: #7b1fa2;
}
.input-group {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.input-field {
flex: 1;
padding: 0.8rem;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 1rem;
}
.btn {
padding: 0.8rem 1.5rem;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
transition: background 0.3s ease;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5a6fd8;
}
.btn-success {
background: #4CAF50;
color: white;
}
.btn-success:hover {
background: #45a049;
}
.btn-danger {
background: #f44336;
color: white;
}
.btn-danger:hover {
background: #da190b;
}
.btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.chat-controls {
margin-bottom: 1rem;
}
.chat-controls input {
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
.back-link {
text-align: center;
margin-top: 2rem;
}
.back-link a {
color: white;
text-decoration: none;
font-weight: 500;
font-size: 1.1rem;
}
.back-link a:hover {
text-decoration: underline;
}
@media (max-width: 768px) {
.demo-grid {
grid-template-columns: 1fr;
}
.header h1 {
font-size: 2rem;
}
body {
padding: 1rem;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🌐 WebSocket演示</h1>
<p>实时通信功能展示 - 支持聊天室和回声测试</p>
</div>
<div class="demo-grid">
<!-- 回声测试 -->
<div class="demo-card">
<div class="demo-header">
<h3>🔄 回声测试</h3>
<p>发送消息,服务器会回声返回</p>
</div>
<div class="demo-content">
<div class="connection-status status-disconnected" id="echo-status">未连接</div>
<div class="message-area" id="echo-messages"></div>
<div class="input-group">
<input type="text" class="input-field" id="echo-input" placeholder="输入消息..." disabled>
<button class="btn btn-primary" id="echo-send" disabled>发送</button>
</div>
<div class="input-group">
<button class="btn btn-success" id="echo-connect">连接</button>
<button class="btn btn-danger" id="echo-disconnect" disabled>断开</button>
<button class="btn btn-primary" id="echo-clear">清空</button>
</div>
</div>
</div>
<!-- 聊天室 -->
<div class="demo-card">
<div class="demo-header">
<h3>💬 聊天室</h3>
<p>多用户实时聊天功能</p>
</div>
<div class="demo-content">
<div class="connection-status status-disconnected" id="chat-status">未连接</div>
<div class="chat-controls">
<input type="text" class="input-field" id="username" placeholder="用户名" value="用户" style="width: 120px;">
<input type="text" class="input-field" id="room-name" placeholder="房间名" value="general" style="width: 120px;">
<button class="btn btn-success" id="chat-connect">加入聊天室</button>
<button class="btn btn-danger" id="chat-disconnect" disabled>离开聊天室</button>
</div>
<div class="message-area" id="chat-messages"></div>
<div class="input-group">
<input type="text" class="input-field" id="chat-input" placeholder="输入消息..." disabled>
<button class="btn btn-primary" id="chat-send" disabled>发送</button>
<button class="btn btn-primary" id="chat-clear">清空</button>
</div>
</div>
</div>
</div>
<div class="back-link">
<a href="/">← 返回首页</a>
</div>
</div>
<script>
// WebSocket连接管理
let echoSocket = null;
let chatSocket = null;
// DOM元素
const echoStatus = document.getElementById('echo-status');
const echoMessages = document.getElementById('echo-messages');
const echoInput = document.getElementById('echo-input');
const echoSendBtn = document.getElementById('echo-send');
const echoConnectBtn = document.getElementById('echo-connect');
const echoDisconnectBtn = document.getElementById('echo-disconnect');
const echoClearBtn = document.getElementById('echo-clear');
const chatStatus = document.getElementById('chat-status');
const chatMessages = document.getElementById('chat-messages');
const chatInput = document.getElementById('chat-input');
const chatSendBtn = document.getElementById('chat-send');
const chatConnectBtn = document.getElementById('chat-connect');
const chatDisconnectBtn = document.getElementById('chat-disconnect');
const chatClearBtn = document.getElementById('chat-clear');
const usernameInput = document.getElementById('username');
const roomNameInput = document.getElementById('room-name');
// 工具函数
function addMessage(container, message, type = 'system') {
const messageDiv = document.createElement('div');
messageDiv.className = `message message-${type}`;
messageDiv.innerHTML = `<strong>[${message.timestamp}]</strong> ${message.message}`;
container.appendChild(messageDiv);
container.scrollTop = container.scrollHeight;
}
function updateStatus(statusElement, status, text) {
statusElement.className = `connection-status status-${status}`;
statusElement.textContent = text;
}
// 回声测试WebSocket
function connectEcho() {
updateStatus(echoStatus, 'connecting', '连接中...');
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws/echo/`;
echoSocket = new WebSocket(wsUrl);
echoSocket.onopen = function(e) {
updateStatus(echoStatus, 'connected', '已连接');
echoInput.disabled = false;
echoSendBtn.disabled = false;
echoConnectBtn.disabled = true;
echoDisconnectBtn.disabled = false;
};
echoSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
console.log('收到WebSocket消息:', data); // 调试信息
let messageType = 'system';
let displayMessage = data;
if (data.type === 'echo_message') {
messageType = 'echo';
// 解码Unicode字符并创建显示用的消息对象
let echoText = data.echo_message;
if (typeof echoText === 'string') {
// 处理可能的Unicode编码
try {
echoText = decodeURIComponent(escape(echoText));
} catch (e) {
// 如果解码失败,使用原始文本
console.log('Unicode解码失败使用原始文本');
}
}
displayMessage = {
message: echoText || data.echo_message || '回声消息',
timestamp: data.timestamp || new Date().toLocaleTimeString()
};
console.log('处理回声消息:', displayMessage); // 调试信息
} else if (data.type === 'error') {
messageType = 'error';
displayMessage = {
message: data.message || '发生错误',
timestamp: data.timestamp || new Date().toLocaleTimeString()
};
} else {
// 处理其他类型的消息
displayMessage = {
message: data.message || JSON.stringify(data),
timestamp: data.timestamp || new Date().toLocaleTimeString()
};
}
addMessage(echoMessages, displayMessage, messageType);
};
echoSocket.onclose = function(e) {
updateStatus(echoStatus, 'disconnected', '已断开');
echoInput.disabled = true;
echoSendBtn.disabled = true;
echoConnectBtn.disabled = false;
echoDisconnectBtn.disabled = true;
};
echoSocket.onerror = function(e) {
updateStatus(echoStatus, 'disconnected', '连接错误');
addMessage(echoMessages, {
message: 'WebSocket连接发生错误',
timestamp: new Date().toLocaleTimeString()
}, 'error');
};
}
function disconnectEcho() {
if (echoSocket) {
echoSocket.close();
}
}
function sendEchoMessage() {
const message = echoInput.value.trim();
if (message && echoSocket && echoSocket.readyState === WebSocket.OPEN) {
echoSocket.send(JSON.stringify({
'message': message
}));
echoInput.value = '';
}
}
// 聊天室WebSocket
function connectChat() {
const username = usernameInput.value.trim() || '匿名用户';
const roomName = roomNameInput.value.trim() || 'general';
updateStatus(chatStatus, 'connecting', '连接中...');
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws/chat/${roomName}/`;
chatSocket = new WebSocket(wsUrl);
chatSocket.onopen = function(e) {
updateStatus(chatStatus, 'connected', `已连接到房间: ${roomName}`);
chatInput.disabled = false;
chatSendBtn.disabled = false;
chatConnectBtn.disabled = true;
chatDisconnectBtn.disabled = false;
usernameInput.disabled = true;
roomNameInput.disabled = true;
// 发送加入通知
chatSocket.send(JSON.stringify({
'type': 'user_join',
'username': username
}));
};
chatSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
let messageType = 'system';
if (data.type === 'chat_message') {
messageType = 'user';
data.message = `${data.username}: ${data.message}`;
} else if (data.type === 'user_notification') {
messageType = 'notification';
} else if (data.type === 'error') {
messageType = 'error';
}
addMessage(chatMessages, data, messageType);
};
chatSocket.onclose = function(e) {
updateStatus(chatStatus, 'disconnected', '已断开');
chatInput.disabled = true;
chatSendBtn.disabled = true;
chatConnectBtn.disabled = false;
chatDisconnectBtn.disabled = true;
usernameInput.disabled = false;
roomNameInput.disabled = false;
};
chatSocket.onerror = function(e) {
updateStatus(chatStatus, 'disconnected', '连接错误');
addMessage(chatMessages, {
message: 'WebSocket连接发生错误',
timestamp: new Date().toLocaleTimeString()
}, 'error');
};
}
function disconnectChat() {
if (chatSocket) {
const username = usernameInput.value.trim() || '匿名用户';
// 发送离开通知
chatSocket.send(JSON.stringify({
'type': 'user_leave',
'username': username
}));
setTimeout(() => {
chatSocket.close();
}, 100);
}
}
function sendChatMessage() {
const message = chatInput.value.trim();
const username = usernameInput.value.trim() || '匿名用户';
if (message && chatSocket && chatSocket.readyState === WebSocket.OPEN) {
chatSocket.send(JSON.stringify({
'type': 'chat_message',
'message': message,
'username': username
}));
chatInput.value = '';
}
}
// 事件监听器
echoConnectBtn.addEventListener('click', connectEcho);
echoDisconnectBtn.addEventListener('click', disconnectEcho);
echoSendBtn.addEventListener('click', sendEchoMessage);
echoClearBtn.addEventListener('click', () => {
echoMessages.innerHTML = '';
});
echoInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendEchoMessage();
}
});
chatConnectBtn.addEventListener('click', connectChat);
chatDisconnectBtn.addEventListener('click', disconnectChat);
chatSendBtn.addEventListener('click', sendChatMessage);
chatClearBtn.addEventListener('click', () => {
chatMessages.innerHTML = '';
});
chatInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendChatMessage();
}
});
// 页面卸载时断开连接
window.addEventListener('beforeunload', function() {
if (echoSocket) {
echoSocket.close();
}
if (chatSocket) {
disconnectChat();
}
});
</script>
</body>
</html>

3
hertz_demo/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

11
hertz_demo/urls.py Normal file
View File

@@ -0,0 +1,11 @@
from django.urls import path
from . import views
app_name = 'hertz_demo'
urlpatterns = [
path('demo/captcha/', views.captcha_demo, name='captcha_demo'),
path('demo/websocket/', views.websocket_demo, name='websocket_demo'),
path('websocket/test/', views.websocket_test, name='websocket_test'),
path('demo/email/', views.email_demo, name='email_demo'),
]

331
hertz_demo/views.py Normal file
View File

@@ -0,0 +1,331 @@
from django.shortcuts import render, redirect
from django.http import JsonResponse, HttpResponse
from django.core.mail import send_mail, EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.contrib import messages
from django import forms
from hertz_studio_django_captcha.captcha_generator import HertzCaptchaGenerator
import json
from django.conf import settings
import random
import string
class HertzCaptchaForm(forms.Form):
"""Hertz验证码表单"""
captcha_input = forms.CharField(
max_length=10,
widget=forms.TextInput(attrs={
'placeholder': '请输入验证码',
'class': 'form-control',
'autocomplete': 'off'
}),
label='验证码'
)
captcha_id = forms.CharField(widget=forms.HiddenInput(), required=False)
def captcha_demo(request):
"""
验证码演示页面
展示多种验证码功能的使用方法
"""
# 获取请求的验证码类型
captcha_type = request.GET.get('type', 'random_char')
# 初始化验证码生成器
captcha_generator = HertzCaptchaGenerator()
if request.method == 'POST':
# 检查是否是Ajax请求
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
try:
data = json.loads(request.body)
action = data.get('action')
if action == 'refresh':
# 刷新验证码
captcha_data = captcha_generator.generate_captcha()
return JsonResponse({
'success': True,
'data': captcha_data
})
elif action == 'verify':
# 验证验证码
captcha_id = data.get('captcha_id', '')
user_input = data.get('user_input', '')
is_valid = captcha_generator.verify_captcha(captcha_id, user_input)
if is_valid:
return JsonResponse({
'success': True,
'valid': True,
'message': f'验证成功!验证码类型: {captcha_type}'
})
else:
return JsonResponse({
'success': True,
'valid': False,
'message': '验证码错误,请重新输入'
})
except json.JSONDecodeError:
return JsonResponse({
'success': False,
'error': '请求数据格式错误'
})
else:
# 普通表单提交处理
form = HertzCaptchaForm(request.POST)
username = request.POST.get('username', '')
captcha_id = request.POST.get('captcha_id', '')
captcha_input = request.POST.get('captcha_input', '')
# 验证验证码
is_valid = captcha_generator.verify_captcha(captcha_id, captcha_input)
if is_valid and username:
# 生成新的验证码用于显示
initial_captcha = captcha_generator.generate_captcha()
return render(request, 'captcha_demo.html', {
'form': HertzCaptchaForm(),
'success_message': f'验证成功!用户名: {username},验证码类型: {captcha_type}',
'captcha_unavailable': False,
'current_type': captcha_type,
'initial_captcha': initial_captcha,
'captcha_types': {
'random_char': '随机字符验证码',
'math': '数学运算验证码',
'word': '单词验证码'
}
})
# GET请求或表单验证失败时生成初始验证码
form = HertzCaptchaForm()
initial_captcha = captcha_generator.generate_captcha()
return render(request, 'captcha_demo.html', {
'form': form,
'captcha_unavailable': False,
'current_type': captcha_type,
'initial_captcha': initial_captcha,
'captcha_types': {
'random_char': '随机字符验证码',
'math': '数学运算验证码',
'word': '单词验证码'
}
})
def websocket_demo(request):
"""WebSocket演示页面"""
return render(request, 'websocket_demo.html')
def websocket_test(request):
"""
WebSocket简单测试页面
"""
return render(request, 'websocket_test.html')
# 测试热重启功能 - 添加注释触发文件变化
def email_demo(request):
"""邮件系统演示页面"""
if request.method == 'GET':
return render(request, 'email_demo.html')
elif request.method == 'POST':
try:
# 获取表单数据
email_type = request.POST.get('email_type', 'welcome')
recipient_email = request.POST.get('recipient_email')
recipient_name = request.POST.get('recipient_name', '用户')
custom_subject = request.POST.get('subject', '')
custom_message = request.POST.get('message', '')
if not recipient_email:
return JsonResponse({
'success': False,
'message': '请输入收件人邮箱地址'
})
# 根据邮件类型生成内容
email_content = generate_email_content(email_type, recipient_name, custom_subject, custom_message)
# 发送邮件
success = send_demo_email(
recipient_email=recipient_email,
subject=email_content['subject'],
html_content=email_content['html_content'],
text_content=email_content['text_content']
)
if success:
return JsonResponse({
'success': True,
'message': f'邮件已成功发送到 {recipient_email}'
})
else:
return JsonResponse({
'success': False,
'message': '邮件发送失败,请检查邮件配置'
})
except Exception as e:
return JsonResponse({
'success': False,
'message': f'发送失败:{str(e)}'
})
def generate_email_content(email_type, recipient_name, custom_subject='', custom_message=''):
"""根据邮件类型生成邮件内容"""
email_templates = {
'welcome': {
'subject': '🎉 欢迎加入 Hertz Server Django',
'html_template': f'''
<html>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="margin: 0; font-size: 28px;">🎉 欢迎加入我们!</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #e1e5e9; border-radius: 0 0 10px 10px;">
<p style="font-size: 16px;">亲爱的 <strong>{recipient_name}</strong></p>
<p>欢迎您注册成为我们的用户!我们很高兴您能加入我们的大家庭。</p>
<p>在这里,您可以享受到:</p>
<ul style="color: #666;">
<li>🔐 安全的验证码系统</li>
<li>🌐 实时WebSocket通信</li>
<li>📧 完善的邮件服务</li>
<li>📚 详细的API文档</li>
</ul>
<p>如果您有任何问题,请随时联系我们。</p>
<div style="text-align: center; margin: 30px 0;">
<a href="http://127.0.0.1:8000/" style="background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 5px; display: inline-block;">开始使用</a>
</div>
<p>祝您使用愉快!</p>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<p style="color: #666; font-size: 14px;">此致<br><strong>Hertz Server Django 团队</strong></p>
</div>
</div>
</body>
</html>
'''
},
'notification': {
'subject': '🔔 系统通知 - Hertz Server Django',
'html_template': f'''
<html>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: #007bff; color: white; padding: 20px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="margin: 0; font-size: 24px;">🔔 系统通知</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #e1e5e9; border-radius: 0 0 10px 10px;">
<p style="font-size: 16px;">亲爱的 <strong>{recipient_name}</strong></p>
<p>您有一条新的系统通知:</p>
<div style="background: #f8f9fa; padding: 20px; border-left: 4px solid #007bff; margin: 20px 0;">
<p style="margin: 0; font-weight: 500;">您的账户设置已更新,如果这不是您的操作,请立即联系我们。</p>
</div>
<p>系统会持续为您提供安全保障,如有疑问请联系客服。</p>
<div style="text-align: center; margin: 30px 0;">
<a href="http://127.0.0.1:8000/" style="background: #007bff; color: white; padding: 12px 30px; text-decoration: none; border-radius: 5px; display: inline-block;">查看详情</a>
</div>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<p style="color: #666; font-size: 14px;">此致<br><strong>Hertz Server Django 团队</strong></p>
</div>
</div>
</body>
</html>
'''
},
'verification': {
'subject': '🔐 邮箱验证 - Hertz Server Django',
'html_template': f'''
<html>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: #28a745; color: white; padding: 20px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="margin: 0; font-size: 24px;">🔐 邮箱验证</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #e1e5e9; border-radius: 0 0 10px 10px;">
<p style="font-size: 16px;">亲爱的 <strong>{recipient_name}</strong></p>
<p>感谢您注册 Hertz Server Django请点击下面的按钮验证您的邮箱地址</p>
<div style="text-align: center; margin: 40px 0;">
<a href="http://127.0.0.1:8000/verify?token=demo_token" style="background: #28a745; color: white; padding: 15px 40px; text-decoration: none; border-radius: 5px; display: inline-block; font-size: 16px; font-weight: 500;">验证邮箱地址</a>
</div>
<p style="color: #666; font-size: 14px;">如果按钮无法点击,请复制以下链接到浏览器:<br>
<code style="background: #f8f9fa; padding: 5px; border-radius: 3px;">http://127.0.0.1:8000/verify?token=demo_token</code></p>
<p style="color: #666;">如果您没有注册账户请忽略此邮件。此验证链接将在24小时后失效。</p>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<p style="color: #666; font-size: 14px;">此致<br><strong>Hertz Server Django 团队</strong></p>
</div>
</div>
</body>
</html>
'''
},
'custom': {
'subject': custom_subject or '自定义邮件 - Hertz Server Django',
'html_template': f'''
<html>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="margin: 0; font-size: 24px;">{custom_subject or '自定义邮件'}</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #e1e5e9; border-radius: 0 0 10px 10px;">
<p style="font-size: 16px;">亲爱的 <strong>{recipient_name}</strong></p>
<div style="margin: 20px 0; font-size: 16px;">
{custom_message.replace(chr(10), '<br>') if custom_message else '这是一封自定义邮件。'}
</div>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<p style="color: #666; font-size: 14px;">此致<br><strong>Hertz Server Django 团队</strong></p>
</div>
</div>
</body>
</html>
'''
}
}
template = email_templates.get(email_type, email_templates['welcome'])
html_content = template['html_template']
text_content = strip_tags(html_content)
return {
'subject': template['subject'],
'html_content': html_content,
'text_content': text_content
}
def send_demo_email(recipient_email, subject, html_content, text_content):
"""发送演示邮件"""
try:
# 检查邮件配置
if not settings.EMAIL_HOST_USER or not settings.EMAIL_HOST_PASSWORD:
print("邮件配置不完整,使用控制台输出模式")
print(f"收件人: {recipient_email}")
print(f"主题: {subject}")
print(f"内容: {text_content[:200]}...")
return True
# 创建邮件
email = EmailMultiAlternatives(
subject=subject,
body=text_content,
from_email=settings.DEFAULT_FROM_EMAIL,
to=[recipient_email]
)
# 添加HTML内容
email.attach_alternative(html_content, "text/html")
# 发送邮件
email.send()
return True
except Exception as e:
print(f"邮件发送失败: {str(e)}")
return False

View File

View File

@@ -0,0 +1,56 @@
"""
ASGI config for hertz_server_django project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
"""
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hertz_server_django.settings')
# Import Django first to ensure proper initialization
from django.core.asgi import get_asgi_application
# Initialize Django ASGI application early to ensure the AppRegistry
# is populated before importing code that may import ORM models.
django_asgi_app = get_asgi_application()
# Import other modules AFTER Django setup
from django.conf import settings
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from channels.security.websocket import AllowedHostsOriginValidator
# Import websocket routing AFTER Django setup to avoid AppRegistryNotReady
from hertz_demo import routing as demo_routing
if 'hertz_studio_django_yolo' in settings.INSTALLED_APPS:
from hertz_studio_django_yolo import routing as yolo_routing
websocket_urlpatterns = (
demo_routing.websocket_urlpatterns +
yolo_routing.websocket_urlpatterns
)
else:
websocket_urlpatterns = demo_routing.websocket_urlpatterns
# 在开发环境下放宽Origin校验便于第三方客户端如 Apifox、wscat调试
websocket_app = AuthMiddlewareStack(
URLRouter(
websocket_urlpatterns
)
)
if getattr(settings, 'DEBUG', False):
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": websocket_app,
})
else:
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": AllowedHostsOriginValidator(websocket_app),
})

View File

@@ -0,0 +1,351 @@
"""
Django settings for hertz_server_django project.
Generated by 'django-admin startproject' using Django 5.2.6.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""
import os
from pathlib import Path
from decouple import config
# 修复DRF的ip_address_validators函数
def fix_drf_ip_validators():
"""
修复DRF的ip_address_validators函数返回值问题
"""
try:
from rest_framework import fields
# 保存原始函数
original_ip_address_validators = fields.ip_address_validators
def fixed_ip_address_validators(protocol, unpack_ipv4):
"""
修复后的ip_address_validators函数确保返回两个值
"""
validators = original_ip_address_validators(protocol, unpack_ipv4)
# 如果只返回了validators添加默认的error_message
if isinstance(validators, list):
return validators, 'Enter a valid IP address.'
else:
# 如果已经返回了两个值,直接返回
return validators
# 应用猴子补丁
fields.ip_address_validators = fixed_ip_address_validators
except ImportError:
# 如果DRF未安装忽略错误
pass
# 应用修复
fix_drf_ip_validators()
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('SECRET_KEY', default='django-insecure-0a1bx*8!97l^4z#ml#ufn_*9ut*)zlso$*k-g^h&(2=p@^51md')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = config('DEBUG', default=True, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost,127.0.0.1', cast=lambda v: [s.strip() for s in v.split(',')])
# Database engine configuration (sqlite/mysql) with backward compatibility
# Prefer `DB_ENGINE` env var; fallback to legacy `USE_REDIS_AS_DB`
DB_ENGINE = config('DB_ENGINE', default=None)
USE_REDIS_AS_DB = config('USE_REDIS_AS_DB', default=True, cast=bool)
if DB_ENGINE is None:
DB_ENGINE = 'sqlite' if USE_REDIS_AS_DB else 'mysql'
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Third party apps
'rest_framework',
'corsheaders',
'channels',
'drf_spectacular',
# 必备注册的app不要删
'hertz_demo', # 初始化演示模块
'hertz_studio_django_captcha', # 验证码模块
'hertz_studio_django_auth', # 权限模块
'hertz_studio_django_system_monitor', # 系统监测模块
'hertz_studio_django_log', # 日志管理模块
'hertz_studio_django_notice', # 通知模块
# ======在下面导入你需要的app======
'hertz_studio_django_ai', #ai聊天模块
'hertz_studio_django_kb', # 知识库 ai和kb库是相互绑定的
'hertz_studio_django_wiki', # 文章模块
'hertz_studio_django_yolo', # YOLO目标检测模块
'hertz_studio_django_yolo_train', # Yolo训练模块
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'hertz_studio_django_auth.utils.middleware.AuthMiddleware', # 权限认证中间件
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'hertz_server_django.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates']
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'hertz_server_django.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
if DB_ENGINE == 'sqlite':
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'data/db.sqlite3',
}
}
# Use Redis-backed sessions when on SQLite (optional, keeps prior behavior)
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'
elif DB_ENGINE == 'mysql':
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': config('DB_NAME', default='hertz_server'),
'USER': config('DB_USER', default='root'),
'PASSWORD': config('DB_PASSWORD', default='root'),
'HOST': config('DB_HOST', default='localhost'),
'PORT': config('DB_PORT', default='3306'),
'OPTIONS': {
'charset': 'utf8mb4',
},
}
}
else:
# Fallback to SQLite for unexpected values
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'data/db.sqlite3',
}
}
# Redis
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': config('REDIS_URL', default='redis://127.0.0.1:6379/0'),
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = 'static/'
STATICFILES_DIRS = [
BASE_DIR / 'static',
]
# Media files (User uploaded files)
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Django REST Framework configuration
# 使用自定义AuthMiddleware进行认证不使用DRF的认证和权限系统
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
'DEFAULT_AUTHENTICATION_CLASSES': [], # 不使用DRF认证类
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny', # 所有接口默认允许访问由AuthMiddleware控制权限
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
}
# Spectacular (OpenAPI 3.0) configuration
SPECTACULAR_SETTINGS = {
'TITLE': 'Hertz Server API',
'DESCRIPTION': 'API documentation for Hertz Server Django project',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
'COMPONENT_SPLIT_REQUEST': True,
'SCHEMA_PATH_PREFIX': '/api/',
}
# CORS configuration
CORS_ALLOWED_ORIGINS = config(
'CORS_ALLOWED_ORIGINS',
default='http://localhost:3000,http://127.0.0.1:3000',
cast=lambda v: [s.strip() for s in v.split(',')]
)
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_ALL_ORIGINS = config('CORS_ALLOW_ALL_ORIGINS', default=False, cast=bool)
# Captcha settings
CAPTCHA_IMAGE_SIZE = (
config('CAPTCHA_IMAGE_SIZE_WIDTH', default=120, cast=int),
config('CAPTCHA_IMAGE_SIZE_HEIGHT', default=50, cast=int)
)
CAPTCHA_LENGTH = config('CAPTCHA_LENGTH', default=4, cast=int)
CAPTCHA_TIMEOUT = config('CAPTCHA_TIMEOUT', default=5, cast=int) # minutes
CAPTCHA_FONT_SIZE = config('CAPTCHA_FONT_SIZE', default=40, cast=int)
CAPTCHA_BACKGROUND_COLOR = config('CAPTCHA_BACKGROUND_COLOR', default='#ffffff')
CAPTCHA_FOREGROUND_COLOR = config('CAPTCHA_FOREGROUND_COLOR', default='#000000')
# 验证码词典文件路径
CAPTCHA_WORDS_DICTIONARY = str(BASE_DIR / 'captcha_words.txt')
# 验证码挑战函数配置
CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.random_char_challenge' # 默认使用随机字符
# 数学验证码配置
CAPTCHA_MATH_CHALLENGE_OPERATOR = '+-*'
# 验证码噪声和过滤器
CAPTCHA_NOISE_FUNCTIONS = (
'captcha.helpers.noise_arcs',
'captcha.helpers.noise_dots',
)
CAPTCHA_FILTER_FUNCTIONS = (
'captcha.helpers.post_smooth',
)
# Email configuration
EMAIL_BACKEND = config('EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend')
EMAIL_HOST = config('EMAIL_HOST', default='smtp.qq.com')
EMAIL_PORT = config('EMAIL_PORT', default=465, cast=int)
EMAIL_USE_SSL = config('EMAIL_USE_SSL', default=True, cast=bool)
EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=False, cast=bool)
EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='563161210@qq.com')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='')
DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', default='563161210@qq.com')
# 注册邮箱验证码开关0=关闭1=开启)
REGISTER_EMAIL_VERIFICATION = config('REGISTER_EMAIL_VERIFICATION', default=0, cast=int)
# Channels configuration for WebSocket support
ASGI_APPLICATION = 'hertz_server_django.asgi.application'
# Channel layers configuration
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [config('REDIS_URL', default='redis://127.0.0.1:6379/2')],
},
},
}
# 自定义用户模型
AUTH_USER_MODEL = 'hertz_studio_django_auth.HertzUser'
# JWT配置
JWT_SECRET_KEY = config('JWT_SECRET_KEY', default=SECRET_KEY)
JWT_ALGORITHM = 'HS256'
JWT_ACCESS_TOKEN_LIFETIME = config('JWT_ACCESS_TOKEN_LIFETIME', default=60 * 60 * 24, cast=int) # 24小时
JWT_REFRESH_TOKEN_LIFETIME = config('JWT_REFRESH_TOKEN_LIFETIME', default=60 * 60 * 24 * 7, cast=int) # 7天
# 权限系统配置
HERTZ_AUTH_SETTINGS = {
'SUPER_ADMIN_PERMISSIONS': ['*'], # 超级管理员拥有所有权限
'DEFAULT_PERMISSIONS': [], # 默认权限
}
# AuthMiddleware配置 - 不需要登录验证的URL模式支持正则表达式
NO_AUTH_PATTERNS = config(
'NO_AUTH_PATTERNS',
default=r'^/api/auth/login/?$,^/api/auth/register/?$,^/api/auth/email/code/?$,^/api/auth/send-email-code/?$,^/api/auth/password/reset/?$,^/api/captcha/.*$,^/api/docs/.*$,^/api/redoc/.*$,^/api/schema/.*$,^/admin/.*$,^/static/.*$,^/media/.*$,^/demo/.*$,^/websocket/.*$,^/api/system/.*$',
cast=lambda v: [s.strip() for s in v.split(',')]
)
# 密码加密配置
PASSWORD_HASHERS = [
'hertz_studio_django_utils.crypto.MD5PasswordHasher', # 使用MD5加密
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]

View File

@@ -0,0 +1,73 @@
"""
URL configuration for hertz_server_django project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
from . import views
urlpatterns = [
# API documentation routes
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
# 首页路由
path('', views.index, name='index'),
# Hertz Captcha routes
path('api/captcha/', include('hertz_studio_django_captcha.urls')),
# Hertz Auth routes
path('api/', include('hertz_studio_django_auth.urls')),
# Demo app routes
path('', include('hertz_demo.urls')),
# Hertz System Monitor routes
path('api/system/', include('hertz_studio_django_system_monitor.urls')),
# Hertz Log routes
path('api/log/', include('hertz_studio_django_log.urls')),
# Hertz Notice routes
path('api/notice/', include('hertz_studio_django_notice.urls')),
# ===========在下面添加你需要的路由===========
# Hertz AI routes
path('api/ai/', include('hertz_studio_django_ai.urls')),
# Hertz Knowledge Base routes
path('api/kb/', include('hertz_studio_django_kb.urls')),
# Hertz Wiki routes
path('api/wiki/', include('hertz_studio_django_wiki.urls')),
# Hertz YOLO routes
path('api/yolo/', include('hertz_studio_django_yolo.urls')),
# YOLO 训练管理
path('api/yolo/train/', include('hertz_studio_django_yolo_train.urls')),
]
# 在开发环境下提供媒体文件服务
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATICFILES_DIRS[0])

View File

@@ -0,0 +1,13 @@
from django.contrib.auth.models import Permission
from django.shortcuts import render
from hertz_studio_django_auth.utils.decorators import no_login_required
@no_login_required
def index(request):
"""
系统首页视图
展示系统的基础介绍和功能特性
"""
return render(request, 'index.html')

View File

@@ -0,0 +1,16 @@
"""
WSGI config for hertz_server_django project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hertz_server_django.settings')
application = get_wsgi_application()

View File

@@ -0,0 +1,25 @@
# EditorConfig配置文件
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[*.{js,ts,vue}]
indent_size = 2
[*.json]
indent_size = 2
[*.{css,scss,sass}]
indent_size = 2

View File

@@ -0,0 +1,10 @@
# API 基础地址
VITE_API_BASE_URL=http://localhost:8000
# 应用配置
VITE_APP_TITLE=Hertz Admin
VITE_APP_VERSION=1.0.0
# 开发服务器配置
VITE_DEV_SERVER_HOST=localhost
VITE_DEV_SERVER_PORT=3000

View File

@@ -0,0 +1,2 @@
VITE_API_BASE_URL=http://localhost:8000
VITE_TEMPLATE_SETUP_MODE=true

View File

@@ -0,0 +1,2 @@
VITE_API_BASE_URL=http://localhost:8000
VITE_TEMPLATE_SETUP_MODE=true

24
hertz_server_django_ui/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,327 @@
<div align="center">
<h1>通用大模型模板 · Hertz Admin + AI</h1>
现代化的管理后台前端模板面向二次开发的前端工程师。内置账号体系、权限路由、主题美化、知识库、YOLO 模型全流程(管理 / 类别 / 告警 / 历史)等典型模块。
<p>
基于 Vite + Vue 3 + TypeScript + Ant Design Vue + Pinia + Vue Router 构建
</p>
</div>
---
## ✨ 特性(面向前端)
- **工程化完善**TS 强类型、模块化 API、统一请求封装、权限化菜单/路由
- **设计统一**:全局“超现代风格”主题,卡片 / 弹窗 / 按钮 / 输入 / 分页风格一致
- **业务可复用**
- 文章管理:分类树 + 列表搜索 + 编辑/发布
- YOLO 模型:模型管理、模型类别管理、告警处理中心、检测历史管理
- AI 助手:多会话列表 + 消息记录 + 多布局对话界面(含错误调试信息)
- 认证体系:登录/注册、验证码
- **可扩展**:清晰的目录划分和命名规范,方便直接加模块或替换现有实现
## 🧩 技术栈
- 构建Vite
- 语言TypeScript
- 框架Vue 3Composition API
- UIAnt Design Vue
- 状态Pinia
- 路由Vue Router
## 📦 项目结构与职责
> 根目录:`通用大模型模板/`
```bash
通用大模型模板/
└─ hertz_server_diango_ui_2/ # 前端工程Vite
├─ public/ # 公共静态资源(不走打包器)
├─ src/
│ ├─ api/ # 接口定义auth / yolo / knowledge / captcha / ai ...
│ │ └─ yolo.ts # YOLO 模型 & 检测 & 类别相关 API
│ ├─ locales/ # 国际化文案
│ ├─ router/ # 路由与菜单配置
│ │ ├─ admin_menu.ts # 管理端菜单 + 路由映射(权限 key
│ │ ├─ user_menu_ai.ts # 用户端菜单 + 路由映射(含 AI 助手)
│ │ └─ index.ts # Vue Router 实例 + 全局路由守卫
│ ├─ stores/ # Pinia Store
│ │ ├─ hertz_app.ts # 全局应用设置(语言、布局、菜单折叠等)
│ │ ├─ hertz_user.ts # 用户 / 鉴权状态
│ │ └─ hertz_theme.ts # 主题配置与 CSS 变量
│ ├─ styles/ # 全局样式与变量
│ │ ├─ index.scss # 全局组件风格覆盖Button / Table / Modal ...
│ │ └─ variables.scss # 主题色、阴影、圆角等变量
│ ├─ utils/ # 工具方法 & 基础设施
│ │ ├─ hertz_request.ts # Axios 封装baseURL、拦截器、错误提示
│ │ ├─ hertz_url.ts # 统一 URL 构造API / 媒体 / WebSocket
│ │ ├─ hertz_env.ts # 读取 & 校验 env 变量
│ │ └─ hertz_router_utils.ts # 路由相关工具 & 调试
│ ├─ views/ # 所有页面
│ │ ├─ admin_page/ # 管理端页面
│ │ │ ├─ ModelManagement.vue # YOLO 模型管理
│ │ │ ├─ AlertLevelManagement.vue # 模型类别管理
│ │ │ ├─ DetectionHistoryManagement.vue # 检测历史管理
│ │ │ └─ ... # 其他管理端模块
│ │ ├─ user_pages/ # 用户端页面(检测端 + AI 助手)
│ │ │ ├─ index.vue # 用户端主布局 + 顶部导航
│ │ │ ├─ AiChat.vue # AI 助手对话页面
│ │ │ ├─ YoloDetection.vue # 离线检测页面
│ │ │ ├─ LiveDetection.vue # 实时检测页面WebSocket
│ │ │ └─ ... # 告警中心 / 通知中心 / 知识库等
│ │ ├─ Login.vue # 登录页
│ │ └─ register.vue # 注册页
│ ├─ App.vue # 应用根组件
│ └─ main.ts # 入口文件(挂载 Vue / 路由 / Pinia
├─ .env.development # 开发环境变量(前端专用)
├─ .env.production # 生产构建环境变量
├─ vite.config.ts # Vite 配置(代理、构建、别名等)
└─ package.json
```
## 📁 文件与命名规范(建议)
- **组件 / 页面**
- 页面:`src/views/admin_page/FooBarManagement.vue`,以业务 + Management 命名
- 纯组件:放到 `src/components/`,使用大驼峰命名,如 `UserSelector.vue`
- **接口文件**
- 同一业务一个文件:`src/api/yolo.ts``src/api/auth.ts`
- 内部导出 `xxxApi` 对象 + TS 类型:`type AlertLevel`, `type YoloModel`
- **样式**
- 全局或主题相关:放 `src/styles/`(注意不要在这里写页面私有样式)
- 单页面样式:使用 `<style scoped lang="scss">` 写在对应 `.vue`
- **工具函数**
- 通用工具:`src/utils/` 下按领域拆分,如 `hertz_url.ts``hertz_env.ts`
## 🌐 后端 IP / 域名配置指引(前端视角最重要)
当前工程已经统一了后端地址配置,只需要 **改 2 个地方**
1. **环境变量文件**(推荐只改这个)
- `hertz_server_diango_ui_2/.env.development`
- `hertz_server_diango_ui_2/.env.production`
两个文件里都有一行:
```bash
# 示例:开发环境
VITE_API_BASE_URL=http://localhost:8000
```
约定:
- **只写协议 + 域名/IP + 端口**,不要包含 `/api`
- ✅ `http://localhost:8000`
- ❌ `http://localhost:8000/api`
- 开发与生产可指向不同后端,只要保证同样的接口路径即可。
2. **Vite 代理 & URL 工具**(已接好,通常不用改)
- `vite.config.ts`
- 利用 `loadEnv` 读取 `VITE_API_BASE_URL`,自动去掉末尾 `/`
```ts
const env = loadEnv(mode, process.cwd(), '')
const apiBaseUrl = env.VITE_API_BASE_URL || 'http://localhost:3000'
const backendOrigin = apiBaseUrl.replace(/\/+$/, '')
```
- 开发环境通过代理转发:
```ts
server: {
proxy: {
'/api': { target: backendOrigin, changeOrigin: true },
'/media': { target: backendOrigin, changeOrigin: true }
}
}
define: {
__VITE_API_BASE_URL__: JSON.stringify(`${backendOrigin}/api`)
}
```
- `src/utils/hertz_url.ts`
- 统一获取后端基础地址:
```ts
export function getBackendBaseUrl(): string {
return import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000'
}
```
- 构造 HTTP / WebSocket / 媒体地址:
```ts
export function getApiBaseUrl() {
return import.meta.env.DEV ? '' : getBackendBaseUrl()
}
export function getMediaBaseUrl() {
if (import.meta.env.DEV) return ''
return getBackendBaseUrl().replace('/api', '')
}
export function getFullFileUrl(relativePath: string) {
const baseURL = getBackendBaseUrl()
return `${baseURL}${relativePath}`
}
```
- `src/utils/hertz_request.ts`
- Axios 实例的 `baseURL` 在开发环境为空字符串(走 Vite 代理);生产环境使用 `VITE_API_BASE_URL`
```ts
const isDev = import.meta.env.DEV
const baseURL = isDev ? '' : (import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000')
```
👉 **结论:前端同事只需要改 `.env.development` 和 `.env.production` 里的 `VITE_API_BASE_URL`,其余 URL 都通过工具/代理自动生效,无需到处搜 `localhost`。**
## 🚀 快速开始
```bash
# 进入工程目录
cd hertz_server_diango_ui_2
# 安装依赖
npm i
# 开发启动(默认 http://localhost:3001
npm run dev
```
## 🔧 关键模块速览
- **主题与 Design System**
- 入口:`src/styles/index.scss`、`src/styles/variables.scss`
- 内容:按钮 / 表格 / 弹窗 / 输入框 等统一风格含毛玻璃、hover、active、focus 细节
- **菜单与路由**
- `src/router/admin_menu.ts`:单文件维护管理端菜单树 + 路由映射 + 权限标识
- 面包屑逻辑已整理:不再重复展示“首页/”,只保留当前层级链路
- **YOLO 模块**
- `ModelManagement.vue`:模型上传 / 列表 / 启用、拖拽上传区
- `AlertLevelManagement.vue`:模型类别管理,支持单条 & 批量修改告警等级
- `DetectionHistoryManagement.vue`:检测历史列表、图片/视频预览
- **认证模块**
- API`src/api/auth.ts`
- 页面:`src/views/Login.vue`、`src/views/register.vue`
- 注册表单字段已与后端约定一致:
`username, password, confirm_password, email, phone, real_name, captcha, captcha_id`
## 🧪 常见问题FAQ
- **需要改哪些地方才能连上新的后端 IP**
- 只改:`.env.development` 和 `.env.production` 的 `VITE_API_BASE_URL`
- 不需要:修改页面内的 `http://localhost:xxxx`,已统一收敛到工具函数
- **接口不走 / 返回字段对不上?**
- 对比:`src/api/*.ts` 里定义的请求路径与 payload
- 打开浏览器 Network 看真实请求 URL、body 与响应
- **页面样式和设计稿不一致?**
- 先看 `src/styles/index.scss` 是否有全局覆盖
- 再查对应 `.vue` 文件中的 scoped 样式是否有特殊处理
## 🛠️ 二次开发建议
- **新增管理模块**
- 在 `src/views/admin_page/` 下新增页面,如 `FooBarManagement.vue`
- 在 `src/router/admin_menu.ts` 中增加菜单配置path + component + permission
- **扩展接口**
- 在 `src/api/` 新增 `xxx.ts`,导出 `xxxApi` 对象
- 使用统一的 `request` 封装(`hertz_request.ts`),保持错误处理一致
- **改造主题 / 品牌色**
- 修改 `src/styles/variables.scss` 中的主色、背景色、圆角、阴影
- 如需大改导航栏、卡片风格,优先在全局样式里做统一,而不是每页重新写
## 🧩 模块选择与模板模式
- **模块配置文件**
- 路径:`src/config/hertz_modules.ts`
- 内容:
- 使用 `HERTZ_MODULES` 统一管理“管理端 / 用户端”各功能模块
- 每个模块包含:`key`(模块标识)、`label`(展示名称)、`group`admin/user、`defaultEnabled`(是否默认启用)
- 运行时通过 `isModuleEnabled` / `getEnabledModuleKeys` 控制路由和菜单是否展示对应模块。
- **模块选择页面(功能 DIY**
- 页面:`src/views/ModuleSetup.vue`
- 路由:`/template/modules`
- 说明:
1. 勾选需要启用的模块,未勾选的模块在菜单和路由中隐藏(仅运行时屏蔽,不改动源码)。
2. 点击“保存配置并刷新”可多次预览效果;点击“保存并跳转登录”会在保存后跳转到登录页。
3. 选择结果会以 `hertz_enabled_modules` 的形式保存在浏览器 Local Storage 中。
- **模板模式开关**
- 通过环境变量控制:`VITE_TEMPLATE_SETUP_MODE`
- 建议在开发环境 (`.env.development`) 中开启:
```bash
VITE_TEMPLATE_SETUP_MODE=true
```
- 当模板模式开启且浏览器中 **没有** `hertz_enabled_modules` 记录时,路由守卫会在首次进入时自动重定向到 `/template/modules`,强制先完成模块选择。
- 如果已经配置过模块,下次 `npm run dev` 将直接进入系统。如需重新进入模块选择页:
1. 打开浏览器开发者工具 → Application → Local Storage
2. 选择当前站点,删除键 `hertz_enabled_modules`
3. 刷新页面即可再次进入模块选择流程。
## ✂️ 一键裁剪npm run prune
> 适用于已经确定“哪些功能模块不再需要”的场景,用于真正瘦身前端代码体积。建议在执行前先提交一次 Git。
- **脚本位置与命令**
- 脚本:`scripts/prune-modules.mjs`
- 命令:
```bash
npm run prune
```
- **推荐使用流程**
1. 启动开发环境:`npm run dev`。
2. 打开 `/template/modules`,通过勾选确认“需要保留的模块”,用“保存配置并刷新”反复调试菜单/路由效果。
3. 确认无误后,关闭开发服务器。
4. 在终端执行 `npm run prune`,按照 CLI 提示:
- 选择要“裁剪掉”的模块(通常是你在模块选择页面中未勾选的模块)。
- 选择裁剪模式:
- **模式 1仅屏蔽**
- 修改 `admin_menu.ts` / `user_menu_ai.ts` 中对应模块的 `moduleKey`,加上 `__pruned__` 前缀
- 注释组件映射行,使这些模块在菜单和路由中完全隐藏
- **不删除任何 `.vue` 文件**,方便后续恢复
- **模式 2删除**
- 在模式 1 的基础上,额外删除对应模块的视图文件,如 `src/views/admin_page/UserManagement.vue` 等
- 这是不可逆操作,建议先在模式 1 下验证,再使用模式 2 做最终瘦身
- **影响范围(前端)**
- 管理端:
- `src/router/admin_menu.ts` 中对应模块的菜单配置和组件映射
- `src/views/admin_page/*.vue` 中不需要的页面(仅在删除模式下移除)
- 用户端:
- `src/router/user_menu_ai.ts` 中对应模块配置
- `src/views/user_pages/*.vue` 中不需要的页面(仅在删除模式下移除)
## 📜 NPM 脚本
```json
{
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview",
"prune": "node scripts/prune-modules.mjs"
}
}
```

80
hertz_server_django_ui/components.d.ts vendored Normal file
View File

@@ -0,0 +1,80 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
// biome-ignore lint: disable
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AAlert: typeof import('ant-design-vue/es')['Alert']
AAvatar: typeof import('ant-design-vue/es')['Avatar']
ABadge: typeof import('ant-design-vue/es')['Badge']
ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb']
ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem']
AButton: typeof import('ant-design-vue/es')['Button']
ACard: typeof import('ant-design-vue/es')['Card']
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup']
ACol: typeof import('ant-design-vue/es')['Col']
ADatePicker: typeof import('ant-design-vue/es')['DatePicker']
ADescriptions: typeof import('ant-design-vue/es')['Descriptions']
ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem']
ADivider: typeof import('ant-design-vue/es')['Divider']
ADrawer: typeof import('ant-design-vue/es')['Drawer']
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
AEmpty: typeof import('ant-design-vue/es')['Empty']
AForm: typeof import('ant-design-vue/es')['Form']
AFormItem: typeof import('ant-design-vue/es')['FormItem']
AImage: typeof import('ant-design-vue/es')['Image']
AInput: typeof import('ant-design-vue/es')['Input']
AInputGroup: typeof import('ant-design-vue/es')['InputGroup']
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
ALayout: typeof import('ant-design-vue/es')['Layout']
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
ALayoutFooter: typeof import('ant-design-vue/es')['LayoutFooter']
ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader']
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
AList: typeof import('ant-design-vue/es')['List']
AListItem: typeof import('ant-design-vue/es')['ListItem']
AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta']
AMenu: typeof import('ant-design-vue/es')['Menu']
AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider']
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
AModal: typeof import('ant-design-vue/es')['Modal']
APagination: typeof import('ant-design-vue/es')['Pagination']
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
AProgress: typeof import('ant-design-vue/es')['Progress']
ARadio: typeof import('ant-design-vue/es')['Radio']
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
AResult: typeof import('ant-design-vue/es')['Result']
ARow: typeof import('ant-design-vue/es')['Row']
ASelect: typeof import('ant-design-vue/es')['Select']
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
ASlider: typeof import('ant-design-vue/es')['Slider']
ASpace: typeof import('ant-design-vue/es')['Space']
ASpin: typeof import('ant-design-vue/es')['Spin']
AStatistic: typeof import('ant-design-vue/es')['Statistic']
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
ASwitch: typeof import('ant-design-vue/es')['Switch']
ATable: typeof import('ant-design-vue/es')['Table']
ATabPane: typeof import('ant-design-vue/es')['TabPane']
ATabs: typeof import('ant-design-vue/es')['Tabs']
ATag: typeof import('ant-design-vue/es')['Tag']
ATextarea: typeof import('ant-design-vue/es')['Textarea']
ATimeline: typeof import('ant-design-vue/es')['Timeline']
ATimelineItem: typeof import('ant-design-vue/es')['TimelineItem']
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
ATree: typeof import('ant-design-vue/es')['Tree']
ATreeSelect: typeof import('ant-design-vue/es')['TreeSelect']
AUpload: typeof import('ant-design-vue/es')['Upload']
AUploadDragger: typeof import('ant-design-vue/es')['UploadDragger']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
}

View File

@@ -0,0 +1,81 @@
import js from '@eslint/js'
import vue from 'eslint-plugin-vue'
import tseslint from 'typescript-eslint'
export default [
// JavaScript 推荐规则
js.configs.recommended,
// TypeScript 推荐规则
...tseslint.configs.recommended,
// Vue 推荐规则
...vue.configs['flat/recommended'],
// 项目特定配置
{
files: ['**/*.{js,mjs,cjs,ts,vue}'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
// 浏览器环境
console: 'readonly',
window: 'readonly',
document: 'readonly',
navigator: 'readonly',
fetch: 'readonly',
// Node.js环境
process: 'readonly',
Buffer: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
module: 'readonly',
require: 'readonly',
global: 'readonly',
// Vite环境
import: 'readonly',
// Vue环境
Vue: 'readonly',
}
},
rules: {
// 禁用所有可能导致WebStorm警告的规则
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'no-console': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'no-debugger': 'off',
'no-alert': 'off',
'no-prototype-builtins': 'off',
// Vue相关规则
'vue/multi-word-component-names': 'off',
'vue/no-unused-vars': 'off',
'vue/no-unused-components': 'off',
'vue/no-unused-properties': 'off',
'vue/require-v-for-key': 'off',
'vue/no-use-v-if-with-v-for': 'off',
// TypeScript规则
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/prefer-const': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
}
},
// 忽略文件
{
ignores: [
'node_modules/**',
'dist/**',
'.git/**',
'coverage/**',
'*.config.js',
'*.config.ts',
]
}
]

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>管理系统模板</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

5673
hertz_server_django_ui/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
{
"name": "hertz_server_django_ui",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview",
"prune": "node scripts/prune-modules.mjs"
},
"dependencies": {
"@types/node": "^24.5.2",
"ant-design-vue": "^3.2.20",
"axios": "^1.12.2",
"echarts": "^6.0.0",
"jszip": "^3.10.1",
"onnxruntime-web": "^1.23.2",
"pinia": "^3.0.3",
"socket.io-client": "^4.8.1",
"vue": "^3.5.21",
"vue-i18n": "^11.1.12",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@types/jszip": "^3.4.0",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1",
"autoprefixer": "^10.4.21",
"eslint": "^9.36.0",
"eslint-plugin-vue": "^10.4.0",
"postcss": "^8.5.6",
"sass-embedded": "^1.93.0",
"typescript": "~5.8.3",
"typescript-eslint": "^8.44.0",
"unplugin-vue-components": "^29.1.0",
"vite": "^7.1.6",
"vue-tsc": "^3.0.7"
}
}

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,392 @@
#!/usr/bin/env node
// 一键裁剪脚本:根据功能模块删除或屏蔽对应的菜单配置和页面文件
// 设计原则:
// - 先通过运行时模块开关/页面确认要保留哪些模块
// - 然后运行本脚本,选择要“裁剪掉”的模块,以及裁剪模式:
// 1) 仅屏蔽(修改 moduleKey使其永远不会被启用保留页面文件
// 2) 删除(在 1 的基础上,再删除对应 .vue 页面文件)
// - 脚本只操作前端代码,不影响后端
import fs from 'fs'
import path from 'path'
import readline from 'readline'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const projectRoot = path.resolve(__dirname, '..')
/** 模块定义(与 src/config/hertz_modules.ts 保持一致) */
const MODULES = [
{ key: 'admin.user-management', label: '管理端 · 用户管理', group: 'admin' },
{ key: 'admin.department-management', label: '管理端 · 部门管理', group: 'admin' },
{ key: 'admin.menu-management', label: '管理端 · 菜单管理', group: 'admin' },
{ key: 'admin.role-management', label: '管理端 · 角色管理', group: 'admin' },
{ key: 'admin.notification-management', label: '管理端 · 通知管理', group: 'admin' },
{ key: 'admin.log-management', label: '管理端 · 日志管理', group: 'admin' },
{ key: 'admin.knowledge-base', label: '管理端 · 文章管理', group: 'admin' },
{ key: 'admin.yolo-model', label: '管理端 · YOLO 模型相关', group: 'admin' },
{ key: 'user.system-monitor', label: '用户端 · 系统监控', group: 'user' },
{ key: 'user.ai-chat', label: '用户端 · AI 助手', group: 'user' },
{ key: 'user.yolo-detection', label: '用户端 · YOLO 检测', group: 'user' },
{ key: 'user.live-detection', label: '用户端 · 实时检测', group: 'user' },
{ key: 'user.detection-history', label: '用户端 · 检测历史', group: 'user' },
{ key: 'user.alert-center', label: '用户端 · 告警中心', group: 'user' },
{ key: 'user.notice-center', label: '用户端 · 通知中心', group: 'user' },
{ key: 'user.knowledge-center', label: '用户端 · 知识库中心', group: 'user' },
]
/**
* 每个模块对应的裁剪配置:
* - adminModuleKey / userModuleKey: 在路由配置文件中的 moduleKey 值
* - adminComponentNames / userComponentNames: 在组件映射对象中的组件名(*.vue
* - viewFiles: 可以安全删除的页面文件(相对项目根路径)
*/
const PRUNE_CONFIG = {
'admin.user-management': {
adminModuleKey: 'admin.user-management',
adminComponentNames: ['UserManagement.vue'],
userModuleKey: null,
userComponentNames: [],
viewFiles: ['src/views/admin_page/UserManagement.vue'],
},
'admin.department-management': {
adminModuleKey: 'admin.department-management',
adminComponentNames: ['DepartmentManagement.vue'],
userModuleKey: null,
userComponentNames: [],
viewFiles: ['src/views/admin_page/DepartmentManagement.vue'],
},
'admin.menu-management': {
adminModuleKey: 'admin.menu-management',
adminComponentNames: ['MenuManagement.vue'],
userModuleKey: null,
userComponentNames: [],
viewFiles: ['src/views/admin_page/MenuManagement.vue'],
},
'admin.role-management': {
adminModuleKey: 'admin.role-management',
adminComponentNames: ['Role.vue'],
userModuleKey: null,
userComponentNames: [],
viewFiles: ['src/views/admin_page/Role.vue'],
},
'admin.notification-management': {
adminModuleKey: 'admin.notification-management',
adminComponentNames: ['NotificationManagement.vue'],
userModuleKey: null,
userComponentNames: [],
viewFiles: ['src/views/admin_page/NotificationManagement.vue'],
},
'admin.log-management': {
adminModuleKey: 'admin.log-management',
adminComponentNames: ['LogManagement.vue'],
userModuleKey: null,
userComponentNames: [],
viewFiles: ['src/views/admin_page/LogManagement.vue'],
},
'admin.knowledge-base': {
adminModuleKey: 'admin.knowledge-base',
adminComponentNames: ['KnowledgeBaseManagement.vue'],
userModuleKey: null,
userComponentNames: [],
viewFiles: ['src/views/admin_page/KnowledgeBaseManagement.vue'],
},
'admin.yolo-model': {
adminModuleKey: 'admin.yolo-model',
adminComponentNames: [
'ModelManagement.vue',
'AlertLevelManagement.vue',
'AlertProcessingCenter.vue',
'DetectionHistoryManagement.vue',
],
userModuleKey: null,
userComponentNames: [],
viewFiles: [
'src/views/admin_page/ModelManagement.vue',
'src/views/admin_page/AlertLevelManagement.vue',
'src/views/admin_page/AlertProcessingCenter.vue',
'src/views/admin_page/DetectionHistoryManagement.vue',
],
},
'user.system-monitor': {
adminModuleKey: null,
adminComponentNames: [],
userModuleKey: 'user.system-monitor',
userComponentNames: ['SystemMonitor.vue'],
viewFiles: ['src/views/user_pages/SystemMonitor.vue'],
},
'user.ai-chat': {
adminModuleKey: null,
adminComponentNames: [],
userModuleKey: 'user.ai-chat',
userComponentNames: ['AiChat.vue'],
viewFiles: ['src/views/user_pages/AiChat.vue'],
},
'user.yolo-detection': {
adminModuleKey: null,
adminComponentNames: [],
userModuleKey: 'user.yolo-detection',
userComponentNames: ['YoloDetection.vue'],
viewFiles: ['src/views/user_pages/YoloDetection.vue'],
},
'user.live-detection': {
adminModuleKey: null,
adminComponentNames: [],
userModuleKey: 'user.live-detection',
userComponentNames: ['LiveDetection.vue'],
viewFiles: ['src/views/user_pages/LiveDetection.vue'],
},
'user.detection-history': {
adminModuleKey: null,
adminComponentNames: [],
userModuleKey: 'user.detection-history',
userComponentNames: ['DetectionHistory.vue'],
viewFiles: ['src/views/user_pages/DetectionHistory.vue'],
},
'user.alert-center': {
adminModuleKey: null,
adminComponentNames: [],
userModuleKey: 'user.alert-center',
userComponentNames: ['AlertCenter.vue'],
viewFiles: ['src/views/user_pages/AlertCenter.vue'],
},
'user.notice-center': {
adminModuleKey: null,
adminComponentNames: [],
userModuleKey: 'user.notice-center',
userComponentNames: ['NoticeCenter.vue'],
viewFiles: ['src/views/user_pages/NoticeCenter.vue'],
},
'user.knowledge-center': {
adminModuleKey: null,
adminComponentNames: [],
userModuleKey: 'user.knowledge-center',
userComponentNames: ['KnowledgeCenter.vue'],
// 注意:这里只删除 KnowledgeCenter.vue保留 KnowledgeDetail.vue避免复杂路由修改
viewFiles: ['src/views/user_pages/KnowledgeCenter.vue'],
},
}
const ADMIN_MENU_FILE = 'src/router/admin_menu.ts'
const USER_MENU_FILE = 'src/router/user_menu_ai.ts'
/**
* 简单的 CLI 交互封装
*/
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
function ask(question) {
return new Promise((resolve) => {
rl.question(question, (answer) => {
resolve(answer.trim())
})
})
}
function resolvePath(relativePath) {
return path.resolve(projectRoot, relativePath)
}
function readTextFile(relativePath) {
const full = resolvePath(relativePath)
if (!fs.existsSync(full)) {
return null
}
return fs.readFileSync(full, 'utf8')
}
function writeTextFile(relativePath, content) {
const full = resolvePath(relativePath)
fs.writeFileSync(full, content, 'utf8')
}
function commentComponentLines(content, componentNames) {
if (!componentNames || componentNames.length === 0) return content
const lines = content.split('\n')
const nameSet = new Set(componentNames)
const updated = lines.map((line) => {
const trimmed = line.trim()
if (trimmed.startsWith('//')) return line
for (const name of nameSet) {
if (line.includes(`'${name}'`)) {
return `// ${line}`
}
}
return line
})
return updated.join('\n')
}
function updateModuleKey(content, originalKey) {
if (!originalKey) return content
const patterns = [
`moduleKey: '${originalKey}'`,
`moduleKey: "${originalKey}"`,
]
const pruned = `moduleKey: '__pruned__${originalKey}'`
if (content.includes(pruned)) {
return content
}
let updated = content
let found = false
for (const p of patterns) {
if (updated.includes(p)) {
updated = updated.replace(p, pruned)
found = true
}
}
if (!found) {
console.warn(`⚠️ 未在文件中找到 moduleKey: ${originalKey}`)
}
return updated
}
function collectViewFilesForModules(selectedKeys) {
const files = new Set()
for (const key of selectedKeys) {
const cfg = PRUNE_CONFIG[key]
if (!cfg) continue
for (const f of cfg.viewFiles) {
files.add(f)
}
}
return Array.from(files)
}
async function main() {
console.log('===== Hertz 模板 · 一键裁剪脚本 =====')
console.log('说明:')
console.log('1. 建议先在浏览器里通过“模板模式 + 模块选择页”确认要保留的模块')
console.log('2. 然后关闭 dev 服务器,运行本脚本选择要裁剪掉的模块')
console.log('3. 先可选择“仅屏蔽”,确认无误后,再选择“删除”彻底缩减代码体积')
console.log('')
console.log('当前可裁剪模块:')
MODULES.forEach((m, index) => {
console.log(`${index + 1}. [${m.group}] ${m.label} (${m.key})`)
})
console.log('')
const indexAnswer = await ask('请输入要“裁剪掉”的模块序号(多个用逗号分隔,例如 2,4,7或直接回车取消')
if (!indexAnswer) {
console.log('未选择任何模块,退出。')
rl.close()
return
}
const indexes = indexAnswer
.split(',')
.map((s) => parseInt(s.trim(), 10))
.filter((n) => !Number.isNaN(n) && n >= 1 && n <= MODULES.length)
if (indexes.length === 0) {
console.log('未解析出有效的序号,退出。')
rl.close()
return
}
const selectedModules = Array.from(new Set(indexes.map((i) => MODULES[i - 1])))
console.log('\n将要裁剪的模块')
selectedModules.forEach((m) => {
console.log(`- [${m.group}] ${m.label} (${m.key})`)
})
console.log('\n裁剪模式')
console.log('1) 仅屏蔽模块:')
console.log(' - 修改 router 配置中的 moduleKey 为 __pruned__...')
console.log(' - 生成的菜单和路由中将完全隐藏这些模块')
console.log(' - 不删除任何 .vue 页面文件(可随时恢复)')
console.log('2) 删除模块:')
console.log(' - 在 1 的基础上,额外删除对应的 .vue 页面文件')
console.log(' - 删除操作不可逆,请确保已经提交或备份代码\n')
const modeAnswer = await ask('请选择裁剪模式1 = 仅屏蔽2 = 删除):')
const mode = modeAnswer === '2' ? 'delete' : 'comment'
const viewFiles = collectViewFilesForModules(selectedModules.map((m) => m.key))
console.log('\n即将进行如下修改')
console.log('- 修改文件: src/router/admin_menu.ts按需')
console.log('- 修改文件: src/router/user_menu_ai.ts按需')
if (mode === 'delete') {
console.log('- 删除页面文件:')
viewFiles.forEach((f) => console.log(` · ${f}`))
} else {
console.log('- 不删除任何页面文件,仅屏蔽模块')
}
const confirm = await ask('\n确认执行这些修改吗(y/N): ')
if (confirm.toLowerCase() !== 'y') {
console.log('已取消操作。')
rl.close()
return
}
// 1) 修改 admin_menu.ts
let adminMenuContent = readTextFile(ADMIN_MENU_FILE)
if (adminMenuContent) {
const adminKeys = selectedModules.map((m) => m.key).filter((k) => PRUNE_CONFIG[k]?.adminModuleKey)
if (adminKeys.length > 0) {
for (const key of adminKeys) {
const cfg = PRUNE_CONFIG[key]
adminMenuContent = updateModuleKey(adminMenuContent, cfg.adminModuleKey)
adminMenuContent = commentComponentLines(adminMenuContent, cfg.adminComponentNames)
}
writeTextFile(ADMIN_MENU_FILE, adminMenuContent)
console.log('✅ 已更新 src/router/admin_menu.ts')
}
}
// 2) 修改 user_menu_ai.ts
let userMenuContent = readTextFile(USER_MENU_FILE)
if (userMenuContent) {
const userKeys = selectedModules.map((m) => m.key).filter((k) => PRUNE_CONFIG[k]?.userModuleKey)
if (userKeys.length > 0) {
for (const key of userKeys) {
const cfg = PRUNE_CONFIG[key]
userMenuContent = updateModuleKey(userMenuContent, cfg.userModuleKey)
userMenuContent = commentComponentLines(userMenuContent, cfg.userComponentNames)
}
writeTextFile(USER_MENU_FILE, userMenuContent)
console.log('✅ 已更新 src/router/user_menu_ai.ts')
}
}
// 3) 删除 .vue 页面文件(仅在 delete 模式下)
if (mode === 'delete') {
console.log('\n开始删除页面文件...')
for (const relative of viewFiles) {
const full = resolvePath(relative)
if (fs.existsSync(full)) {
fs.rmSync(full)
console.log(`🗑️ 已删除: ${relative}`)
} else {
console.log(`⚠️ 文件不存在,跳过: ${relative}`)
}
}
}
console.log('\n🎉 裁剪完成。建议执行以下操作检查:')
console.log('- 重新运行: npm run dev')
console.log('- 在浏览器中确认菜单和路由是否符合预期')
console.log('- 如需恢复,请使用 Git 回退或重新拷贝模板')
rl.close()
}
main().catch((err) => {
console.error('执行过程中发生错误:', err)
rl.close()
process.exit(1)
})

View File

@@ -0,0 +1,85 @@
<script setup lang="ts">
import { computed, ref, onMounted } from 'vue'
import { useUserStore } from './stores/hertz_user'
import { RouterView } from 'vue-router'
import { ConfigProvider } from 'ant-design-vue'
import zhCN from 'ant-design-vue/es/locale/zh_CN'
import enUS from 'ant-design-vue/es/locale/en_US'
const userStore = useUserStore()
// 主题配置 - 简约现代风格
const theme = ref({
algorithm: 'default' as 'default' | 'dark' | 'compact',
token: {
colorPrimary: '#2563eb',
colorSuccess: '#10b981',
colorWarning: '#f59e0b',
colorError: '#ef4444',
borderRadius: 8,
fontSize: 14,
},
})
// 语言配置
const locale = ref(zhCN)
// 主题切换
const toggleTheme = () => {
const currentTheme = localStorage.getItem('theme') || 'light'
const newTheme = currentTheme === 'light' ? 'dark' : 'light'
localStorage.setItem('theme', newTheme)
if (newTheme === 'dark') {
theme.value.algorithm = 'dark'
document.documentElement.setAttribute('data-theme', 'dark')
} else {
theme.value.algorithm = 'default'
document.documentElement.setAttribute('data-theme', 'light')
}
}
// 初始化主题
onMounted(() => {
const savedTheme = localStorage.getItem('theme') || 'light'
if (savedTheme === 'dark') {
theme.value.algorithm = 'dark'
document.documentElement.setAttribute('data-theme', 'dark')
} else {
theme.value.algorithm = 'default'
document.documentElement.setAttribute('data-theme', 'light')
}
})
const showLayout = computed(() => {
return userStore.isLoggedIn
})
</script>
<template>
<div id="app">
<ConfigProvider :theme="theme" :locale="locale">
<RouterView />
</ConfigProvider>
</div>
</template>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#app {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
padding: 0;
}
</style>

View File

@@ -0,0 +1,96 @@
import { request } from '@/utils/hertz_request'
// 通用响应类型
export interface ApiResponse<T> {
success: boolean
code: number
message: string
data: T
}
// 会话与消息类型
export interface AIChatItem {
id: number
title: string
created_at: string
updated_at: string
latest_message?: string
}
export interface AIChatDetail {
id: number
title: string
created_at: string
updated_at: string
}
export interface AIChatMessage {
id: number
role: 'user' | 'assistant' | 'system'
content: string
created_at: string
}
export interface ChatListData {
total: number
page: number
page_size: number
chats: AIChatItem[]
}
export interface ChatDetailData {
chat: AIChatDetail
messages: AIChatMessage[]
}
export interface SendMessageData {
user_message: AIChatMessage
ai_message: AIChatMessage
}
// 将后端可能返回的 chat_id 统一规范为 id
const normalizeChatItem = (raw: any): AIChatItem => ({
id: typeof raw?.id === 'number' ? raw.id : Number(raw?.chat_id),
title: raw?.title,
created_at: raw?.created_at,
updated_at: raw?.updated_at,
latest_message: raw?.latest_message,
})
const normalizeChatDetail = (raw: any): AIChatDetail => ({
id: typeof raw?.id === 'number' ? raw.id : Number(raw?.chat_id),
title: raw?.title,
created_at: raw?.created_at,
updated_at: raw?.updated_at,
})
export const aiApi = {
listChats: (params?: { query?: string; page?: number; page_size?: number }): Promise<ApiResponse<ChatListData>> =>
request.get('/api/ai/chats/', { params, showError: false }).then((resp: any) => {
if (resp?.data?.chats && Array.isArray(resp.data.chats)) {
resp.data.chats = resp.data.chats.map((c: any) => normalizeChatItem(c))
}
return resp as ApiResponse<ChatListData>
}),
createChat: (body?: { title?: string }): Promise<ApiResponse<AIChatDetail>> =>
request.post('/api/ai/chats/create/', body || { title: '新对话' }).then((resp: any) => {
if (resp?.data) resp.data = normalizeChatDetail(resp.data)
return resp as ApiResponse<AIChatDetail>
}),
getChatDetail: (chatId: number): Promise<ApiResponse<ChatDetailData>> =>
request.get(`/api/ai/chats/${chatId}/`).then((resp: any) => {
if (resp?.data?.chat) resp.data.chat = normalizeChatDetail(resp.data.chat)
return resp as ApiResponse<ChatDetailData>
}),
updateChat: (chatId: number, body: { title: string }): Promise<ApiResponse<null>> =>
request.put(`/api/ai/chats/${chatId}/update/`, body),
deleteChats: (chatIds: number[]): Promise<ApiResponse<null>> =>
request.post('/api/ai/chats/delete/', { chat_ids: chatIds }),
sendMessage: (chatId: number, body: { content: string }): Promise<ApiResponse<SendMessageData>> =>
request.post(`/api/ai/chats/${chatId}/send/`, body),
}

View File

@@ -0,0 +1,47 @@
import { request } from '@/utils/hertz_request'
// 注册接口数据类型
export interface RegisterData {
username: string
password: string
confirm_password: string
email: string
phone: string
real_name: string
captcha: string
captcha_id: string
}
// 发送邮箱验证码数据类型
export interface SendEmailCodeData {
email: string
code_type: string
}
// 登录接口数据类型
export interface LoginData {
username: string
password: string
captcha_code: string
captcha_key: string
}
// 注册API
export const registerUser = (data: RegisterData) => {
return request.post('/api/auth/register/', data)
}
// 登录API
export const loginUser = (data: LoginData) => {
return request.post('/api/auth/login/', data)
}
// 发送邮箱验证码API
export const sendEmailCode = (data: SendEmailCodeData) => {
return request.post('/api/auth/email/code/', data)
}
// 登出API
export const logoutUser = () => {
return request.post('/api/auth/logout/')
}

View File

@@ -0,0 +1,89 @@
import { request } from '@/utils/hertz_request'
// 验证码相关接口类型定义
export interface CaptchaResponse {
captcha_id: string
image_data: string // base64编码的图片
expires_in: number // 过期时间(秒)
}
export interface CaptchaRefreshResponse {
captcha_id: string
image_data: string // base64编码的图片
expires_in: number // 过期时间(秒)
}
/**
* 生成验证码
*/
export const generateCaptcha = async (): Promise<CaptchaResponse> => {
console.log('🚀 开始发送验证码生成请求...')
console.log('📍 请求URL:', `${import.meta.env.VITE_API_BASE_URL}/api/captcha/generate/`)
console.log('🌐 环境变量 VITE_API_BASE_URL:', import.meta.env.VITE_API_BASE_URL)
try {
const response = await request.post<{
code: number
message: string
data: CaptchaResponse
}>('/api/captcha/generate/')
console.log('✅ 验证码生成请求成功:', response)
return response.data
} catch (error: any) {
console.error('❌ 验证码生成请求失败 - 完整错误信息:')
console.error('错误对象:', error)
console.error('错误类型:', typeof error)
console.error('错误消息:', error?.message)
console.error('错误代码:', error?.code)
console.error('错误状态:', error?.status)
console.error('错误响应:', error?.response)
console.error('错误请求:', error?.request)
console.error('错误配置:', error?.config)
// 检查是否是网络错误
if (error?.code === 'NETWORK_ERROR' || error?.message?.includes('Network Error')) {
console.error('🌐 网络连接错误 - 可能的原因:')
console.error('1. 后端服务器未启动')
console.error('2. API地址不正确')
console.error('3. CORS配置问题')
console.error('4. 防火墙阻止连接')
}
throw error
}
}
/**
* 刷新验证码
*/
export const refreshCaptcha = async (captcha_id: string): Promise<CaptchaRefreshResponse> => {
console.log('🔄 开始发送验证码刷新请求...')
console.log('📍 请求URL:', `${import.meta.env.VITE_API_BASE_URL}/api/captcha/refresh/`)
console.log('📦 请求数据:', { captcha_id })
try {
const response = await request.post<{
code: number
message: string
data: CaptchaRefreshResponse
}>('/api/captcha/refresh/', {
captcha_id
})
console.log('✅ 验证码刷新请求成功:', response)
return response.data
} catch (error: any) {
console.error('❌ 验证码刷新请求失败 - 完整错误信息:')
console.error('错误对象:', error)
console.error('错误类型:', typeof error)
console.error('错误消息:', error?.message)
console.error('错误代码:', error?.code)
console.error('错误状态:', error?.status)
console.error('错误响应:', error?.response)
console.error('错误请求:', error?.request)
console.error('错误配置:', error?.config)
throw error
}
}

View File

@@ -0,0 +1,393 @@
import { request } from '@/utils/hertz_request'
import { logApi, type OperationLogListItem } from './log'
import { systemMonitorApi, type SystemInfo, type CpuInfo, type MemoryInfo, type DiskInfo } from './system_monitor'
import { noticeUserApi } from './notice_user'
import { knowledgeApi } from './knowledge'
// 仪表盘统计数据类型定义
export interface DashboardStats {
totalUsers: number
totalNotifications: number
totalLogs: number
totalKnowledge: number
userGrowthRate: number
notificationGrowthRate: number
logGrowthRate: number
knowledgeGrowthRate: number
}
// 最近活动数据类型
export interface RecentActivity {
id: number
action: string
time: string
user: string
type: 'login' | 'create' | 'update' | 'system' | 'register'
}
// 系统状态数据类型
export interface SystemStatus {
cpuUsage: number
memoryUsage: number
diskUsage: number
networkStatus: 'normal' | 'warning' | 'error'
}
// 访问趋势数据类型
export interface VisitTrend {
date: string
visits: number
users: number
}
// 仪表盘数据汇总类型
export interface DashboardData {
stats: DashboardStats
recentActivities: RecentActivity[]
systemStatus: SystemStatus
visitTrends: VisitTrend[]
}
// API响应类型
export interface ApiResponse<T> {
success: boolean
code: number
message: string
data: T
}
// 仪表盘API接口
export const dashboardApi = {
// 获取仪表盘统计数据
getStats: (): Promise<ApiResponse<DashboardStats>> => {
return request.get('/api/dashboard/stats/')
},
// 获取真实统计数据
getRealStats: async (): Promise<ApiResponse<DashboardStats>> => {
try {
// 并行获取各种统计数据
const [notificationStats, logStats, knowledgeStats] = await Promise.all([
noticeUserApi.statistics().catch(() => ({ success: false, data: { total_count: 0, unread_count: 0 } })),
logApi.getList({ page: 1, page_size: 1 }).catch(() => ({ success: false, data: { count: 0 } })),
knowledgeApi.getArticles({ page: 1, page_size: 1 }).catch(() => ({ success: false, data: { total: 0 } }))
])
// 计算统计数据
const totalNotifications = notificationStats.success ? (notificationStats.data.total_count || 0) : 0
// 处理日志数据 - 兼容多种返回结构
let totalLogs = 0
if (logStats.success && logStats.data) {
const logData = logStats.data as any
console.log('日志API响应数据:', logData)
// 兼容DRF标准结构{ count, next, previous, results }
if ('count' in logData) {
totalLogs = Number(logData.count) || 0
} else if ('total' in logData) {
totalLogs = Number(logData.total) || 0
} else if ('total_count' in logData) {
totalLogs = Number(logData.total_count) || 0
} else if (logData.pagination && logData.pagination.total_count) {
totalLogs = Number(logData.pagination.total_count) || 0
}
console.log('解析出的日志总数:', totalLogs)
} else {
console.log('日志API调用失败:', logStats)
}
const totalKnowledge = knowledgeStats.success ? (knowledgeStats.data.total || 0) : 0
console.log('统计数据汇总:', { totalNotifications, totalLogs, totalKnowledge })
// 模拟增长率(实际项目中应该从后端获取)
const stats: DashboardStats = {
totalUsers: 0, // 暂时设为0需要用户管理API
totalNotifications,
totalLogs,
totalKnowledge,
userGrowthRate: 0,
notificationGrowthRate: Math.floor(Math.random() * 20) - 10, // 模拟 -10% 到 +10%
logGrowthRate: Math.floor(Math.random() * 30) - 15, // 模拟 -15% 到 +15%
knowledgeGrowthRate: Math.floor(Math.random() * 25) - 12 // 模拟 -12% 到 +13%
}
return {
success: true,
code: 200,
message: 'success',
data: stats
}
} catch (error) {
console.error('获取真实统计数据失败:', error)
return {
success: false,
code: 500,
message: '获取统计数据失败',
data: {
totalUsers: 0,
totalNotifications: 0,
totalLogs: 0,
totalKnowledge: 0,
userGrowthRate: 0,
notificationGrowthRate: 0,
logGrowthRate: 0,
knowledgeGrowthRate: 0
}
}
}
},
// 获取最近活动(从日志接口)
getRecentActivities: async (limit: number = 10): Promise<ApiResponse<RecentActivity[]>> => {
try {
const response = await logApi.getList({ page: 1, page_size: limit })
if (response.success && response.data) {
// 根据实际API响应结构数据可能在data.logs或data.results中
const logs = (response.data as any).logs || (response.data as any).results || []
const activities: RecentActivity[] = logs.map((log: any) => ({
id: log.log_id || log.id,
action: log.description || log.operation_description || `${log.action_type_display || log.operation_type} - ${log.module || log.operation_module}`,
time: formatTimeAgo(log.created_at),
user: log.username || log.user?.username || '未知用户',
type: mapLogTypeToActivityType(log.action_type || log.operation_type)
}))
return {
success: true,
code: 200,
message: 'success',
data: activities
}
}
return {
success: false,
code: 500,
message: '获取活动数据失败',
data: []
}
} catch (error) {
console.error('获取最近活动失败:', error)
return {
success: false,
code: 500,
message: '获取活动数据失败',
data: []
}
}
},
// 获取系统状态(从系统监控接口)
getSystemStatus: async (): Promise<ApiResponse<SystemStatus>> => {
try {
const [cpuResponse, memoryResponse, disksResponse] = await Promise.all([
systemMonitorApi.getCpu(),
systemMonitorApi.getMemory(),
systemMonitorApi.getDisks()
])
if (cpuResponse.success && memoryResponse.success && disksResponse.success) {
// 根据实际API响应结构映射数据
const systemStatus: SystemStatus = {
// CPU使用率从 cpu_percent 字段获取
cpuUsage: Math.round(cpuResponse.data.cpu_percent || 0),
// 内存使用率:从 percent 字段获取
memoryUsage: Math.round(memoryResponse.data.percent || 0),
// 磁盘使用率:从磁盘数组的第一个磁盘的 percent 字段获取
diskUsage: disksResponse.data.length > 0 ? Math.round(disksResponse.data[0].percent || 0) : 0,
networkStatus: 'normal' as const
}
return {
success: true,
code: 200,
message: 'success',
data: systemStatus
}
}
return {
success: false,
code: 500,
message: '获取系统状态失败',
data: {
cpuUsage: 0,
memoryUsage: 0,
diskUsage: 0,
networkStatus: 'error' as const
}
}
} catch (error) {
console.error('获取系统状态失败:', error)
return {
success: false,
code: 500,
message: '获取系统状态失败',
data: {
cpuUsage: 0,
memoryUsage: 0,
diskUsage: 0,
networkStatus: 'error' as const
}
}
}
},
// 获取访问趋势
getVisitTrends: (period: 'week' | 'month' | 'year' = 'week'): Promise<ApiResponse<VisitTrend[]>> => {
return request.get('/api/dashboard/visit-trends/', { params: { period } })
},
// 获取完整仪表盘数据
getDashboardData: (): Promise<ApiResponse<DashboardData>> => {
return request.get('/api/dashboard/overview/')
},
// 模拟数据方法(用于开发阶段)
getMockStats: (): Promise<DashboardStats> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
totalUsers: 1128,
todayVisits: 893,
totalOrders: 234,
totalRevenue: 12560.50,
userGrowthRate: 12,
visitGrowthRate: 8,
orderGrowthRate: -3,
revenueGrowthRate: 15
})
}, 500)
})
},
getMockActivities: (): Promise<RecentActivity[]> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
id: 1,
action: '用户 张三 登录了系统',
time: '2分钟前',
user: '张三',
type: 'login'
},
{
id: 2,
action: '管理员 李四 创建了新部门',
time: '5分钟前',
user: '李四',
type: 'create'
},
{
id: 3,
action: '用户 王五 修改了个人信息',
time: '10分钟前',
user: '王五',
type: 'update'
},
{
id: 4,
action: '系统自动备份完成',
time: '1小时前',
user: '系统',
type: 'system'
},
{
id: 5,
action: '新用户 赵六 注册成功',
time: '2小时前',
user: '赵六',
type: 'register'
}
])
}, 300)
})
},
getMockSystemStatus: (): Promise<SystemStatus> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
cpuUsage: 45,
memoryUsage: 67,
diskUsage: 32,
networkStatus: 'normal'
})
}, 200)
})
},
getMockVisitTrends: (period: 'week' | 'month' | 'year' = 'week'): Promise<VisitTrend[]> => {
return new Promise((resolve) => {
setTimeout(() => {
const data = {
week: [
{ date: '周一', visits: 120, users: 80 },
{ date: '周二', visits: 150, users: 95 },
{ date: '周三', visits: 180, users: 110 },
{ date: '周四', visits: 200, users: 130 },
{ date: '周五', visits: 250, users: 160 },
{ date: '周六', visits: 180, users: 120 },
{ date: '周日', visits: 160, users: 100 }
],
month: [
{ date: '第1周', visits: 800, users: 500 },
{ date: '第2周', visits: 950, users: 600 },
{ date: '第3周', visits: 1100, users: 700 },
{ date: '第4周', visits: 1200, users: 750 }
],
year: [
{ date: '1月', visits: 3200, users: 2000 },
{ date: '2月', visits: 3800, users: 2400 },
{ date: '3月', visits: 4200, users: 2600 },
{ date: '4月', visits: 3900, users: 2300 },
{ date: '5月', visits: 4500, users: 2800 },
{ date: '6月', visits: 5000, users: 3100 }
]
}
resolve(data[period])
}, 400)
})
}
}
// 辅助函数:格式化时间为相对时间
function formatTimeAgo(dateString: string): string {
const now = new Date()
const date = new Date(dateString)
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000)
if (diffInSeconds < 60) {
return `${diffInSeconds}秒前`
} else if (diffInSeconds < 3600) {
const minutes = Math.floor(diffInSeconds / 60)
return `${minutes}分钟前`
} else if (diffInSeconds < 86400) {
const hours = Math.floor(diffInSeconds / 3600)
return `${hours}小时前`
} else {
const days = Math.floor(diffInSeconds / 86400)
return `${days}天前`
}
}
// 辅助函数:将日志操作类型映射为活动类型
function mapLogTypeToActivityType(operationType: string): RecentActivity['type'] {
if (!operationType) return 'system'
const lowerType = operationType.toLowerCase()
if (lowerType.includes('login') || lowerType.includes('登录')) {
return 'login'
} else if (lowerType.includes('create') || lowerType.includes('创建') || lowerType.includes('add') || lowerType.includes('新增')) {
return 'create'
} else if (lowerType.includes('update') || lowerType.includes('修改') || lowerType.includes('edit') || lowerType.includes('更新')) {
return 'update'
} else if (lowerType.includes('register') || lowerType.includes('注册')) {
return 'register'
} else if (lowerType.includes('view') || lowerType.includes('查看') || lowerType.includes('get') || lowerType.includes('获取')) {
return 'system'
} else {
return 'system'
}
}

View File

@@ -0,0 +1,93 @@
import { request } from '@/utils/hertz_request'
// 部门数据类型定义
export interface Department {
dept_id: number
parent_id: number | null
dept_name: string
dept_code: string
leader: string
phone: string | null
email: string | null
status: number
sort_order: number
created_at: string
updated_at: string
children?: Department[]
user_count?: number
}
// API响应类型
export interface ApiResponse<T> {
success: boolean
code: number
message: string
data: T
}
// 部门列表数据类型
export interface DepartmentListData {
list: Department[]
total: number
page: number
page_size: number
}
export type DepartmentListResponse = ApiResponse<DepartmentListData>
// 部门列表查询参数
export interface DepartmentListParams {
page?: number
page_size?: number
search?: string
status?: number
parent_id?: number
}
// 创建部门参数
export interface CreateDepartmentParams {
parent_id: null
dept_name: string
dept_code: string
leader: string
phone: string
email: string
status: number
sort_order: number
}
// 更新部门参数
export type UpdateDepartmentParams = Partial<CreateDepartmentParams>
// 部门API接口
export const departmentApi = {
// 获取部门列表
getDepartmentList: (params?: DepartmentListParams): Promise<ApiResponse<Department[]>> => {
return request.get('/api/departments/', { params })
},
// 获取部门详情
getDepartment: (id: number): Promise<ApiResponse<Department>> => {
return request.get(`/api/departments/${id}/`)
},
// 创建部门
createDepartment: (data: CreateDepartmentParams): Promise<ApiResponse<Department>> => {
return request.post('/api/departments/create/', data)
},
// 更新部门
updateDepartment: (id: number, data: UpdateDepartmentParams): Promise<ApiResponse<Department>> => {
return request.put(`/api/departments/${id}/update/`, data)
},
// 删除部门
deleteDepartment: (id: number): Promise<ApiResponse<any>> => {
return request.delete(`/api/departments/${id}/delete/`)
},
// 获取部门树
getDepartmentTree: (): Promise<ApiResponse<Department[]>> => {
return request.get('/api/departments/tree/')
}
}

View File

@@ -0,0 +1,17 @@
// API 统一出口文件
export * from './captcha'
export * from './auth'
export * from './user'
export * from './department'
export * from './menu'
export * from './role'
export * from './password'
export * from './system_monitor'
export * from './dashboard'
export * from './ai'
// 这里可以继续添加其它 API 模块的导出,例如:
// export * from './admin'
export * from './log'
export * from './knowledge'
export * from './kb'

View File

@@ -0,0 +1,131 @@
import { request } from '@/utils/hertz_request'
// 通用响应结构(与后端 HertzResponse 对齐)
export interface KbApiResponse<T> {
success: boolean
code: number
message: string
data: T
}
// 知识库条目
export interface KbItem {
id: number
title: string
modality: 'text' | 'code' | 'image' | 'audio' | 'video' | string
source_type: 'text' | 'file' | 'url' | string
chunk_count?: number
created_at?: string
updated_at?: string
created_chunk_count?: number
// 允许后端扩展字段
[key: string]: any
}
export interface KbItemListParams {
query?: string
page?: number
page_size?: number
}
export interface KbItemListData {
total: number
page: number
page_size: number
list: KbItem[]
}
// 语义搜索
export interface KbSearchParams {
q: string
k?: number
}
// 问答RAG
export interface KbQaPayload {
question: string
k?: number
}
export interface KbQaData {
answer: string
[key: string]: any
}
// 图谱查询参数(实体 / 关系)
export interface KbGraphListParams {
query?: string
page?: number
page_size?: number
// 关系检索可选参数
source?: number
target?: number
relation_type?: string
}
export const kbApi = {
// 知识库条目:列表
listItems(params?: KbItemListParams): Promise<KbApiResponse<KbItemListData>> {
return request.get('/api/kb/items/list/', { params })
},
// 语义搜索
search(params: KbSearchParams): Promise<KbApiResponse<any>> {
return request.get('/api/kb/search/', { params })
},
// 问答RAG
qa(payload: KbQaPayload): Promise<KbApiResponse<KbQaData>> {
return request.post('/api/kb/qa/', payload)
},
// 图谱:实体列表
listEntities(params?: KbGraphListParams): Promise<KbApiResponse<any>> {
return request.get('/api/kb/graph/entities/', { params })
},
// 图谱:关系列表
listRelations(params?: KbGraphListParams): Promise<KbApiResponse<any>> {
return request.get('/api/kb/graph/relations/', { params })
},
// 知识库条目创建JSON 文本)
createItemJson(payload: { title: string; modality?: string; source_type?: string; content?: string; metadata?: any }): Promise<KbApiResponse<KbItem>> {
return request.post('/api/kb/items/create/', payload)
},
// 知识库条目:创建(文件上传)
createItemFile(formData: FormData): Promise<KbApiResponse<KbItem>> {
return request.post('/api/kb/items/create/', formData)
},
// 图谱:创建实体
createEntity(payload: { name: string; type: string; properties?: any }): Promise<KbApiResponse<any>> {
return request.post('/api/kb/graph/entities/', payload)
},
// 图谱:更新实体
updateEntity(id: number, payload: { name?: string; type?: string; properties?: any }): Promise<KbApiResponse<any>> {
return request.put(`/api/kb/graph/entities/${id}/`, payload)
},
// 图谱:删除实体
deleteEntity(id: number): Promise<KbApiResponse<null>> {
return request.delete(`/api/kb/graph/entities/${id}/`)
},
// 图谱:创建关系
createRelation(payload: { source: number; target: number; relation_type: string; properties?: any; source_chunk?: number }): Promise<KbApiResponse<any>> {
return request.post('/api/kb/graph/relations/', payload)
},
// 图谱:删除关系
deleteRelation(id: number): Promise<KbApiResponse<null>> {
return request.delete(`/api/kb/graph/relations/${id}/`)
},
// 图谱:自动抽取实体与关系
extractGraph(payload: { text?: string; item_id?: number }): Promise<KbApiResponse<{ entities: number; relations: number }>> {
return request.post('/api/kb/graph/extract/', payload)
},
}

View File

@@ -0,0 +1,173 @@
import { request } from '@/utils/hertz_request'
// 通用响应结构
export interface ApiResponse<T> {
success: boolean
code: number
message: string
data: T
}
// 分类类型
export interface KnowledgeCategory {
id: number
name: string
description?: string
parent?: number | null
parent_name?: string | null
sort_order?: number
is_active?: boolean
created_at?: string
updated_at?: string
children_count?: number
articles_count?: number
full_path?: string
children?: KnowledgeCategory[]
}
export interface CategoryListData {
list: KnowledgeCategory[]
total: number
page: number
page_size: number
}
export interface CategoryListParams {
page?: number
page_size?: number
name?: string
parent_id?: number
is_active?: boolean
}
// 文章类型
export interface KnowledgeArticleListItem {
id: number
title: string
summary?: string | null
image?: string | null
category_name: string
author_name: string
status: 'draft' | 'published' | 'archived'
status_display: string
view_count?: number
created_at: string
updated_at: string
published_at?: string | null
}
export interface KnowledgeArticleDetail extends KnowledgeArticleListItem {
content: string
category: number
author: number
tags?: string
tags_list?: string[]
sort_order?: number
}
export interface ArticleListData {
list: KnowledgeArticleListItem[]
total: number
page: number
page_size: number
}
export interface ArticleListParams {
page?: number
page_size?: number
title?: string
category_id?: number
author_id?: number
status?: 'draft' | 'published' | 'archived'
tags?: string
}
export interface CreateArticlePayload {
title: string
content: string
summary?: string
image?: string
category: number
status?: 'draft' | 'published'
tags?: string
sort_order?: number
}
export interface UpdateArticlePayload {
title?: string
content?: string
summary?: string
image?: string
category?: number
status?: 'draft' | 'published' | 'archived'
tags?: string
sort_order?: number
}
// 知识库 API
export const knowledgeApi = {
// 分类:列表
getCategories: (params?: CategoryListParams): Promise<ApiResponse<CategoryListData>> => {
return request.get('/api/wiki/categories/', { params })
},
// 分类:树形
getCategoryTree: (): Promise<ApiResponse<KnowledgeCategory[]>> => {
return request.get('/api/wiki/categories/tree/')
},
// 分类:详情
getCategory: (id: number): Promise<ApiResponse<KnowledgeCategory>> => {
return request.get(`/api/wiki/categories/${id}/`)
},
// 分类:创建
createCategory: (data: Partial<KnowledgeCategory>): Promise<ApiResponse<KnowledgeCategory>> => {
return request.post('/api/wiki/categories/create/', data)
},
// 分类:更新
updateCategory: (id: number, data: Partial<KnowledgeCategory>): Promise<ApiResponse<KnowledgeCategory>> => {
return request.put(`/api/wiki/categories/${id}/update/`, data)
},
// 分类:删除
deleteCategory: (id: number): Promise<ApiResponse<null>> => {
return request.delete(`/api/wiki/categories/${id}/delete/`)
},
// 文章:列表
getArticles: (params?: ArticleListParams): Promise<ApiResponse<ArticleListData>> => {
return request.get('/api/wiki/articles/', { params })
},
// 文章:详情
getArticle: (id: number): Promise<ApiResponse<KnowledgeArticleDetail>> => {
return request.get(`/api/wiki/articles/${id}/`)
},
// 文章:创建
createArticle: (data: CreateArticlePayload): Promise<ApiResponse<KnowledgeArticleDetail>> => {
return request.post('/api/wiki/articles/create/', data)
},
// 文章:更新
updateArticle: (id: number, data: UpdateArticlePayload): Promise<ApiResponse<KnowledgeArticleDetail>> => {
return request.put(`/api/wiki/articles/${id}/update/`, data)
},
// 文章:删除
deleteArticle: (id: number): Promise<ApiResponse<null>> => {
return request.delete(`/api/wiki/articles/${id}/delete/`)
},
// 文章:发布
publishArticle: (id: number): Promise<ApiResponse<null>> => {
return request.post(`/api/wiki/articles/${id}/publish/`)
},
// 文章:归档
archiveArticle: (id: number): Promise<ApiResponse<null>> => {
return request.post(`/api/wiki/articles/${id}/archive/`)
},
}

View File

@@ -0,0 +1,110 @@
import { request } from '@/utils/hertz_request'
// 通用 API 响应结构
export interface ApiResponse<T> {
success: boolean
code: number
message: string
data: T
}
// 列表查询参数
export interface LogListParams {
page?: number
page_size?: number
user_id?: number
operation_type?: string
operation_module?: string
start_date?: string // YYYY-MM-DD
end_date?: string // YYYY-MM-DD
ip_address?: string
status?: number
// 新增:按请求方法与路径、关键字筛选(与后端保持可选兼容)
request_method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | string
request_path?: string
keyword?: string
}
// 列表项(精简字段)
export interface OperationLogItem {
id: number
user?: {
id: number
username: string
email?: string
} | null
operation_type: string
// 展示字段
action_type_display?: string
operation_module: string
operation_description?: string
target_model?: string
target_object_id?: string
ip_address?: string
request_method: string
request_path: string
response_status: number
// 结果与状态展示
status_display?: string
is_success?: boolean
execution_time?: number
created_at: string
}
// 列表响应 data 结构
export interface LogListData {
count: number
next: string | null
previous: string | null
results: OperationLogItem[]
}
export type LogListResponse = ApiResponse<LogListData>
// 详情数据(完整字段)
export interface OperationLogDetail {
id: number
user?: {
id: number
username: string
email?: string
} | null
operation_type: string
action_type_display?: string
operation_module: string
operation_description: string
target_model?: string
target_object_id?: string
ip_address?: string
user_agent?: string
request_method: string
request_path: string
request_data?: Record<string, any>
response_status: number
status_display?: string
is_success?: boolean
response_data?: Record<string, any>
execution_time?: number
created_at: string
updated_at?: string
}
export type LogDetailResponse = ApiResponse<OperationLogDetail>
export const logApi = {
// 获取操作日志列表
getList: (params: LogListParams, options?: { signal?: AbortSignal }): Promise<LogListResponse> => {
// 关闭统一错误弹窗,由页面自行处理
return request.get('/api/log/list/', { params, showError: false, signal: options?.signal })
},
// 获取操作日志详情
getDetail: (logId: number): Promise<LogDetailResponse> => {
return request.get(`/api/log/detail/${logId}/`)
},
// 兼容查询参数方式的详情(部分后端实现为 /api/log/detail/?id=xx 或 ?log_id=xx
getDetailByQuery: (logId: number): Promise<LogDetailResponse> => {
return request.get('/api/log/detail/', { params: { id: logId, log_id: logId } })
},
}

View File

@@ -0,0 +1,361 @@
import { request } from '@/utils/hertz_request'
// 后端返回的原始菜单数据格式
export interface RawMenu {
menu_id: number
menu_name: string
menu_code: string
menu_type: number // 后端返回数字1=菜单, 2=按钮, 3=接口
parent_id?: number | null
path?: string
component?: string | null
icon?: string
permission?: string
sort_order?: number
description?: string
status?: number
is_external?: boolean
is_cache?: boolean
is_visible?: boolean
created_at?: string
updated_at?: string
children?: RawMenu[]
}
// 前端使用的菜单接口类型定义
export interface Menu {
menu_id: number
menu_name: string
menu_code: string
menu_type: number // 1=菜单, 2=按钮, 3=接口
parent_id?: number
path?: string
component?: string
icon?: string
permission?: string
sort_order?: number
status?: number
is_external?: boolean
is_cache?: boolean
is_visible?: boolean
created_at?: string
updated_at?: string
children?: Menu[]
}
// API响应基础结构
export interface ApiResponse<T> {
success: boolean
code: number
message: string
data: T
}
// 菜单列表数据结构
export interface MenuListData {
list: Menu[]
total: number
page: number
page_size: number
}
// 菜单列表响应类型
export type MenuListResponse = ApiResponse<MenuListData>
// 菜单列表查询参数
export interface MenuListParams {
page?: number
page_size?: number
search?: string
status?: number
menu_type?: string
parent_id?: number
}
// 创建菜单参数
export interface CreateMenuParams {
menu_name: string
menu_code: string
menu_type: number // 1=菜单, 2=按钮, 3=接口
parent_id?: number
path?: string
component?: string
icon?: string
permission?: string
sort_order?: number
status?: number
is_external?: boolean
is_cache?: boolean
is_visible?: boolean
}
// 更新菜单参数
export type UpdateMenuParams = Partial<CreateMenuParams>
// 菜单树响应类型
export type MenuTreeResponse = ApiResponse<Menu[]>
// 数据转换工具函数
const convertMenuType = (type: number): 'menu' | 'button' | 'api' => {
switch (type) {
case 1: return 'menu'
case 2: return 'button'
case 3: return 'api'
default: return 'menu'
}
}
// 解码Unicode字符串
const decodeUnicode = (str: string): string => {
try {
return str.replace(/\\u[\dA-F]{4}/gi, (match) => {
return String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16))
})
} catch (error) {
return str
}
}
// 转换原始菜单数据为前端格式
const transformRawMenu = (rawMenu: RawMenu): Menu => {
// 确保status字段被正确转换
let statusValue: number
if (rawMenu.status === undefined || rawMenu.status === null) {
// 如果status缺失默认为启用1
statusValue = 1
} else {
// 如果有值,转换为数字
if (typeof rawMenu.status === 'string') {
const parsed = parseInt(rawMenu.status, 10)
statusValue = isNaN(parsed) ? 1 : parsed
} else {
statusValue = Number(rawMenu.status)
// 如果转换失败,默认为启用
if (isNaN(statusValue)) {
statusValue = 1
}
}
}
return {
menu_id: rawMenu.menu_id,
menu_name: decodeUnicode(rawMenu.menu_name),
menu_code: rawMenu.menu_code,
menu_type: rawMenu.menu_type,
parent_id: rawMenu.parent_id || undefined,
path: rawMenu.path,
component: rawMenu.component,
icon: rawMenu.icon,
permission: rawMenu.permission,
sort_order: rawMenu.sort_order,
status: statusValue, // 使用转换后的值
is_external: rawMenu.is_external,
is_cache: rawMenu.is_cache,
is_visible: rawMenu.is_visible,
created_at: rawMenu.created_at,
updated_at: rawMenu.updated_at,
children: rawMenu.children ? rawMenu.children.map(transformRawMenu) : []
}
}
// 将菜单数据数组转换为列表格式
const transformToMenuList = (rawMenus: RawMenu[]): MenuListData => {
const transformedMenus = rawMenus.map(transformRawMenu)
// 递归收集所有菜单项
const collectAllMenus = (menu: Menu): Menu[] => {
const result = [menu]
if (menu.children && menu.children.length > 0) {
menu.children.forEach(child => {
result.push(...collectAllMenus(child))
})
}
return result
}
// 收集所有菜单项
const allMenus: Menu[] = []
transformedMenus.forEach(menu => {
allMenus.push(...collectAllMenus(menu))
})
return {
list: allMenus,
total: allMenus.length,
page: 1,
page_size: allMenus.length
}
}
// 构建菜单树结构
const buildMenuTree = (rawMenus: RawMenu[]): Menu[] => {
const transformedMenus = rawMenus.map(transformRawMenu)
// 创建菜单映射
const menuMap = new Map<number, Menu>()
transformedMenus.forEach(menu => {
menuMap.set(menu.menu_id, { ...menu, children: [] })
})
// 构建树结构
const rootMenus: Menu[] = []
transformedMenus.forEach(menu => {
const menuItem = menuMap.get(menu.menu_id)!
if (menu.parent_id && menuMap.has(menu.parent_id)) {
const parent = menuMap.get(menu.parent_id)!
if (!parent.children) parent.children = []
parent.children.push(menuItem)
} else {
rootMenus.push(menuItem)
}
})
return rootMenus
}
// 菜单API
export const menuApi = {
// 获取菜单列表
getMenuList: async (params?: MenuListParams): Promise<MenuListResponse> => {
try {
const response = await request.get<ApiResponse<RawMenu[]>>('/api/menus/', { params })
if (response.success && response.data && Array.isArray(response.data)) {
const menuListData = transformToMenuList(response.data)
return {
success: true,
code: response.code,
message: response.message,
data: menuListData
}
}
return {
success: false,
code: response.code || 500,
message: response.message || '获取菜单数据失败',
data: {
list: [],
total: 0,
page: 1,
page_size: 10
}
}
} catch (error) {
console.error('获取菜单列表失败:', error)
return {
success: false,
code: 500,
message: '网络请求失败',
data: {
list: [],
total: 0,
page: 1,
page_size: 10
}
}
}
},
// 获取菜单树
getMenuTree: async (): Promise<MenuTreeResponse> => {
try {
const response = await request.get<ApiResponse<RawMenu[]>>('/api/menus/tree/')
if (response.success && response.data && Array.isArray(response.data)) {
// 调试检查原始数据中的status值
if (response.data.length > 0) {
console.log('🔍 原始菜单数据status检查前5条:', response.data.slice(0, 5).map((m: RawMenu) => ({
menu_name: m.menu_name,
menu_id: m.menu_id,
status: m.status,
statusType: typeof m.status
})))
}
// 后端已经返回树形结构,直接转换数据格式即可
const transformedData = response.data.map(transformRawMenu)
// 调试检查转换后的status值
if (transformedData.length > 0) {
console.log('🔍 转换后菜单数据status检查前5条:', transformedData.slice(0, 5).map((m: Menu) => ({
menu_name: m.menu_name,
menu_id: m.menu_id,
status: m.status,
statusType: typeof m.status
})))
}
return {
success: true,
code: response.code,
message: response.message,
data: transformedData
}
}
return {
success: false,
code: response.code || 500,
message: response.message || '获取菜单树失败',
data: []
}
} catch (error) {
console.error('获取菜单树失败:', error)
return {
success: false,
code: 500,
message: '网络请求失败',
data: []
}
}
},
// 获取单个菜单
getMenu: async (id: number): Promise<ApiResponse<Menu>> => {
try {
const response = await request.get<ApiResponse<RawMenu>>(`/api/menus/${id}/`)
if (response.success && response.data) {
const transformedMenu = transformRawMenu(response.data)
return {
success: true,
code: response.code,
message: response.message,
data: transformedMenu
}
}
return response as ApiResponse<Menu>
} catch (error) {
console.error('获取菜单详情失败:', error)
return {
success: false,
code: 500,
message: '网络请求失败',
data: {} as Menu
}
}
},
// 创建菜单
createMenu: (data: CreateMenuParams): Promise<ApiResponse<Menu>> => {
return request.post('/api/menus/create/', data)
},
// 更新菜单
updateMenu: (id: number, data: UpdateMenuParams): Promise<ApiResponse<Menu>> => {
return request.put(`/api/menus/${id}/update/`, data)
},
// 删除菜单
deleteMenu: (id: number): Promise<ApiResponse<any>> => {
return request.delete(`/api/menus/${id}/delete/`)
},
// 批量删除菜单
batchDeleteMenus: (ids: number[]): Promise<ApiResponse<any>> => {
return request.post('/api/menus/batch-delete/', { menu_ids: ids })
}
}

View File

@@ -0,0 +1,87 @@
import { request } from '@/utils/hertz_request'
// 用户端通知模块 API 类型定义
export interface UserNoticeListItem {
notice: number
title: string
notice_type_display: string
priority_display: string
is_top: boolean
publish_time: string
is_read: boolean
read_time: string | null
is_starred: boolean
starred_time: string | null
is_expired: boolean
created_at: string
}
export interface UserNoticeListData {
notices: UserNoticeListItem[]
pagination: {
current_page: number
page_size: number
total_pages: number
total_count: number
has_next: boolean
has_previous: boolean
}
statistics: {
total_count: number
unread_count: number
starred_count: number
}
}
export interface ApiResponse<T = any> {
success: boolean
code: number
message: string
data: T
}
export interface UserNoticeDetailData {
notice: number
title: string
content: string
notice_type_display: string
priority_display: string
attachment_url: string | null
publish_time: string
expire_time: string
is_top: boolean
is_expired: boolean
publisher_name: string | null
is_read: boolean
read_time: string
is_starred: boolean
starred_time: string | null
created_at: string
updated_at: string
}
export const noticeUserApi = {
// 查看通知列表
list: (params?: { page?: number; page_size?: number }): Promise<ApiResponse<UserNoticeListData>> =>
request.get('/api/notice/user/list/', { params }),
// 查看通知详情
detail: (notice_id: number | string): Promise<ApiResponse<UserNoticeDetailData>> =>
request.get(`/api/notice/user/detail/${notice_id}/`),
// 标记通知已读
markRead: (notice_id: number | string): Promise<ApiResponse<null>> =>
request.post('/api/notice/user/mark-read/', { notice_id }),
// 批量标记通知已读
batchMarkRead: (notice_ids: Array<number | string>): Promise<ApiResponse<{ updated_count: number }>> =>
request.post('/api/notice/user/batch-mark-read/', { notice_ids }),
// 用户获取通知统计
statistics: (): Promise<ApiResponse<{ total_count: number; unread_count: number; read_count: number; starred_count: number; type_statistics?: Record<string, number>; priority_statistics?: Record<string, number> }>> =>
request.get('/api/notice/user/statistics/'),
// 收藏/取消收藏通知
toggleStar: (notice_id: number | string, is_starred: boolean): Promise<ApiResponse<null>> =>
request.post('/api/notice/user/toggle-star/', { notice_id, is_starred }),
}

View File

@@ -0,0 +1,31 @@
import { request } from '@/utils/hertz_request'
// 修改密码接口参数
export interface ChangePasswordParams {
old_password: string
new_password: string
confirm_password: string
}
// 重置密码接口参数
export interface ResetPasswordParams {
email: string
email_code: string
new_password: string
confirm_password: string
}
// 修改密码
export const changePassword = (params: ChangePasswordParams) => {
return request.post('/api/auth/password/change/', params)
}
// 重置密码
export const resetPassword = (params: ResetPasswordParams) => {
return request.post('/api/auth/password/reset/', params)
}
// 发送重置密码邮箱验证码
export const sendResetPasswordCode = (email: string) => {
return request.post('/api/auth/password/reset/code/', { email })
}

View File

@@ -0,0 +1,130 @@
import { request } from '@/utils/hertz_request'
// 权限接口类型定义
export interface Permission {
permission_id: number
permission_name: string
permission_code: string
permission_type: 'menu' | 'button' | 'api'
parent_id?: number
path?: string
icon?: string
sort_order?: number
description?: string
status?: number
children?: Permission[]
}
// 角色接口类型定义
export interface Role {
role_id: number
role_name: string
role_code: string
description?: string
status?: number
created_at?: string
updated_at?: string
permissions?: Permission[]
}
// API响应基础结构
export interface ApiResponse<T> {
success: boolean
code: number
message: string
data: T
}
// 角色列表数据结构
export interface RoleListData {
list: Role[]
total: number
page: number
page_size: number
}
// 角色列表响应类型
export type RoleListResponse = ApiResponse<RoleListData>
// 角色列表查询参数
export interface RoleListParams {
page?: number
page_size?: number
search?: string
status?: number
}
// 创建角色参数
export interface CreateRoleParams {
role_name: string
role_code: string
description?: string
status?: number
}
// 更新角色参数
export type UpdateRoleParams = Partial<CreateRoleParams>
// 角色权限分配参数
export interface AssignRolePermissionsParams {
role_id: number
menu_ids: number[]
user_type?: number
department_id?: number
}
// 权限列表响应类型
export type PermissionListResponse = ApiResponse<Permission[]>
// 角色API
export const roleApi = {
// 获取角色列表
getRoleList: (params?: RoleListParams): Promise<RoleListResponse> => {
return request.get('/api/roles/', { params })
},
// 获取单个角色
getRole: (id: number): Promise<ApiResponse<Role>> => {
return request.get(`/api/roles/${id}/`)
},
// 创建角色
createRole: (data: CreateRoleParams): Promise<ApiResponse<Role>> => {
return request.post('/api/roles/create/', data)
},
// 更新角色
updateRole: (id: number, data: UpdateRoleParams): Promise<ApiResponse<Role>> => {
return request.put(`/api/roles/${id}/update/`, data)
},
// 删除角色
deleteRole: (id: number): Promise<ApiResponse<any>> => {
return request.delete(`/api/roles/${id}/delete/`)
},
// 批量删除角色
batchDeleteRoles: (ids: number[]): Promise<ApiResponse<any>> => {
return request.post('/api/roles/batch-delete/', { role_ids: ids })
},
// 获取角色权限
getRolePermissions: (id: number): Promise<ApiResponse<Permission[]>> => {
return request.get(`/api/roles/${id}/menus/`)
},
// 分配角色权限
assignRolePermissions: (data: AssignRolePermissionsParams): Promise<ApiResponse<any>> => {
return request.post(`/api/roles/assign-menus/`, data)
},
// 获取所有权限列表
getPermissionList: (): Promise<PermissionListResponse> => {
return request.get('/api/menus/')
},
// 获取权限树
getPermissionTree: (): Promise<PermissionListResponse> => {
return request.get('/api/menus/tree/')
}
}

View File

@@ -0,0 +1,114 @@
import { request } from '@/utils/hertz_request'
// 通用响应类型
export interface ApiResponse<T> {
success: boolean
code: number
message: string
data: T
}
// 1. 系统信息
export interface SystemInfo {
hostname: string
platform: string
architecture: string
boot_time: string
uptime: string
}
// 2. CPU 信息
export interface CpuInfo {
cpu_count: number
cpu_percent: number
cpu_freq: {
current: number
min: number
max: number
}
load_avg: number[]
}
// 3. 内存信息
export interface MemoryInfo {
total: number
available: number
used: number
percent: number
free: number
}
// 4. 磁盘信息
export interface DiskInfo {
device: string
mountpoint: string
fstype: string
total: number
used: number
free: number
percent: number
}
// 5. 网络信息
export interface NetworkInfo {
interface: string
bytes_sent: number
bytes_recv: number
packets_sent: number
packets_recv: number
}
// 6. 进程信息
export interface ProcessInfo {
pid: number
name: string
status: string
cpu_percent: number
memory_percent: number
memory_info: {
rss: number
vms: number
}
create_time: string
cmdline: string[]
}
// 7. GPU 信息
export interface GpuInfoItem {
id: number
name: string
load: number
memory_total: number
memory_used: number
memory_util: number
temperature: number
}
export interface GpuInfoResponse {
gpu_available: boolean
gpu_info?: GpuInfoItem[]
message?: string
timestamp: string
}
// 8. 综合监测信息
export interface MonitorData {
system: SystemInfo
cpu: CpuInfo
memory: MemoryInfo
disks: DiskInfo[]
network: NetworkInfo[]
processes: ProcessInfo[]
gpus: Array<{ gpu_available: boolean; message?: string; timestamp: string }>
}
export const systemMonitorApi = {
getSystem: (): Promise<ApiResponse<SystemInfo>> => request.get('/api/system/system/'),
getCpu: (): Promise<ApiResponse<CpuInfo>> => request.get('/api/system/cpu/'),
getMemory: (): Promise<ApiResponse<MemoryInfo>> => request.get('/api/system/memory/'),
getDisks: (): Promise<ApiResponse<DiskInfo[]>> => request.get('/api/system/disks/'),
getNetwork: (): Promise<ApiResponse<NetworkInfo[]>> => request.get('/api/system/network/'),
getProcesses: (): Promise<ApiResponse<ProcessInfo[]>> => request.get('/api/system/processes/'),
getGpu: (): Promise<ApiResponse<GpuInfoResponse>> => request.get('/api/system/gpu/'),
getMonitor: (): Promise<ApiResponse<MonitorData>> => request.get('/api/system/monitor/'),
}

View File

@@ -0,0 +1,121 @@
import { request } from '@/utils/hertz_request'
// 角色接口类型定义
export interface Role {
role_id: number
role_name: string
role_code: string
role_ids?: string
}
// 用户接口类型定义(匹配后端实际数据结构)
export interface User {
user_id: number
username: string
email: string
phone?: string
real_name?: string
avatar?: string
gender: number
birthday?: string
department_id?: number
status: number
last_login_time?: string
last_login_ip?: string
created_at: string
updated_at: string
roles: Role[]
}
// API响应基础结构
export interface ApiResponse<T> {
success: boolean
code: number
message: string
data: T
}
// 用户列表数据结构
export interface UserListData {
list: User[]
total: number
page: number
page_size: number
}
// 用户列表响应类型
export type UserListResponse = ApiResponse<UserListData>
// 用户列表查询参数
export interface UserListParams {
page?: number
page_size?: number
search?: string
status?: number
role_ids?: string
}
// 分配角色参数
export interface AssignRolesParams {
user_id: number
role_ids: number[] // 角色ID数组
}
// 用户API
export const userApi = {
// 获取用户列表
getUserList: (params?: UserListParams): Promise<UserListResponse> => {
return request.get('/api/users/', { params })
},
// 获取单个用户
getUser: (id: number): Promise<ApiResponse<User>> => {
return request.get(`/api/users/${id}/`)
},
// 创建用户
createUser: (data: Partial<User>): Promise<ApiResponse<User>> => {
return request.post('/api/users/create/', data)
},
// 更新用户
updateUser: (id: number, data: Partial<User>): Promise<ApiResponse<User>> => {
return request.put(`/api/users/${id}/update/`, data)
},
// 删除用户
deleteUser: (id: number): Promise<ApiResponse<any>> => {
return request.delete(`/api/users/${id}/delete/`)
},
// 批量删除用户
batchDeleteUsers: (ids: number[]): Promise<ApiResponse<any>> => {
return request.post('/api/admin/users/batch-delete/', { user_ids: ids })
},
// 获取当前用户信息
getUserInfo: (): Promise<ApiResponse<User>> => {
return request.get('/api/auth/user/info/')
},
// 更新当前用户信息
updateUserInfo: (data: Partial<User>): Promise<ApiResponse<User>> => {
return request.put('/api/auth/user/info/update/', data)
},
uploadAvatar: (file: File): Promise<ApiResponse<User>> => {
const formData = new FormData()
formData.append('avatar', file)
return request.upload('/api/auth/user/avatar/upload/', formData)
},
// 分配用户角色
assignRoles: (data: AssignRolesParams): Promise<ApiResponse<any>> => {
return request.post('/api/users/assign-roles/', data)
},
// 获取所有角色列表
getRoleList: (): Promise<ApiResponse<Role[]>> => {
return request.get('/api/roles/')
}
}

View File

@@ -0,0 +1,643 @@
import { request } from '@/utils/hertz_request'
// YOLO检测相关接口类型定义
export interface YoloDetectionRequest {
image: File
model_id?: string
confidence_threshold?: number
nms_threshold?: number
}
export interface DetectionBbox {
x: number
y: number
width: number
height: number
}
export interface YoloDetection {
class_id: number
class_name: string
confidence: number
bbox: DetectionBbox
}
export interface YoloDetectionResponse {
message: string
data?: {
detection_id: number
result_file_url: string
original_file_url: string
object_count: number
detected_categories: string[]
confidence_scores: number[]
avg_confidence: number | null
processing_time: number
model_used: string
confidence_threshold: number
user_id: number
user_name: string
alert_level?: 'low' | 'medium' | 'high'
}
}
export interface YoloModel {
id: string
name: string
version: string
description: string
classes: string[]
is_active: boolean
is_enabled: boolean
model_file: string
model_folder_path: string
model_path: string
weights_folder_path: string
categories: { [key: string]: any }
created_at: string
updated_at: string
}
export interface YoloModelListResponse {
success: boolean
message?: string
data?: {
models: YoloModel[]
total: number
}
}
// 数据集管理相关类型
export interface YoloDatasetSummary {
id: number
name: string
version?: string
root_folder_path: string
data_yaml_path: string
nc?: number
description?: string
created_at?: string
}
export interface YoloDatasetDetail extends YoloDatasetSummary {
names?: string[]
train_images_count?: number
train_labels_count?: number
val_images_count?: number
val_labels_count?: number
test_images_count?: number
test_labels_count?: number
}
export interface YoloDatasetSampleItem {
image: string
image_size?: number
label?: string
filename: string
}
// YOLO 训练任务相关类型
export type YoloTrainStatus =
| 'queued'
| 'running'
| 'canceling'
| 'completed'
| 'failed'
| 'canceled'
export interface YoloTrainDatasetOption {
id: number
name: string
version?: string
yaml: string
}
export interface YoloTrainVersionOption {
family: 'v8' | '11' | '12'
config_path: string
sizes: string[]
}
export interface YoloTrainOptionsResponse {
success: boolean
code?: number
message?: string
data?: {
datasets: YoloTrainDatasetOption[]
versions: YoloTrainVersionOption[]
}
}
export interface YoloTrainingJob {
id: number
dataset: number
dataset_name: string
model_family: 'v8' | '11' | '12'
model_size?: 'n' | 's' | 'm' | 'l' | 'x'
weight_path?: string
config_path?: string
status: YoloTrainStatus
logs_path?: string
runs_path?: string
best_model_path?: string
last_model_path?: string
progress: number
epochs: number
imgsz: number
batch: number
device: string
optimizer: 'SGD' | 'Adam' | 'AdamW' | 'RMSProp'
error_message?: string
created_at: string
started_at?: string | null
finished_at?: string | null
}
export interface StartTrainingPayload {
dataset_id: number
model_family: 'v8' | '11' | '12'
model_size?: 'n' | 's' | 'm' | 'l' | 'x'
epochs?: number
imgsz?: number
batch?: number
device?: string
optimizer?: 'SGD' | 'Adam' | 'AdamW' | 'RMSProp'
}
export interface YoloTrainLogsResponse {
success: boolean
code?: number
message?: string
data?: {
content: string
next_offset: number
finished: boolean
}
}
// YOLO检测API
export const yoloApi = {
// 执行YOLO检测
async detectImage(detectionRequest: YoloDetectionRequest): Promise<YoloDetectionResponse> {
console.log('🔍 构建检测请求:', detectionRequest)
console.log('📁 文件对象详情:', {
name: detectionRequest.image.name,
size: detectionRequest.image.size,
type: detectionRequest.image.type,
lastModified: detectionRequest.image.lastModified
})
const formData = new FormData()
formData.append('file', detectionRequest.image)
if (detectionRequest.model_id) {
formData.append('model_id', detectionRequest.model_id)
}
if (detectionRequest.confidence_threshold) {
formData.append('confidence_threshold', detectionRequest.confidence_threshold.toString())
}
if (detectionRequest.nms_threshold) {
formData.append('nms_threshold', detectionRequest.nms_threshold.toString())
}
// 调试FormData内容
console.log('📤 FormData内容:')
for (const [key, value] of formData.entries()) {
if (value instanceof File) {
console.log(` ${key}: File(${value.name}, ${value.size} bytes, ${value.type})`)
} else {
console.log(` ${key}:`, value)
}
}
return request.post('/api/yolo/detect/', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
},
// 获取当前启用的YOLO模型信息
async getCurrentEnabledModel(): Promise<{ success: boolean; data?: YoloModel; message?: string }> {
// 关闭全局错误提示,由调用方(如 YOLO 检测页面)自行处理“未启用模型”等业务文案
return request.get('/api/yolo/models/enabled/', { showError: false })
},
// 获取模型详情
async getModelInfo(modelId: string): Promise<{ success: boolean; data?: YoloModel; message?: string }> {
return request.get(`/api/yolo/models/${modelId}`)
},
// 批量检测
async detectBatch(images: File[], modelId?: string): Promise<YoloDetectionResponse[]> {
const promises = images.map(image =>
this.detectImage({
image,
model_id: modelId,
confidence_threshold: 0.5,
nms_threshold: 0.4
})
)
return Promise.all(promises)
},
// 获取模型列表
async getModels(): Promise<{ success: boolean; data?: YoloModel[]; message?: string }> {
return request.get('/api/yolo/models/')
},
// 上传模型
async uploadModel(formData: FormData): Promise<{ success: boolean; message?: string }> {
// 使用专门的upload方法它会自动处理Content-Type
return request.upload('/api/yolo/upload/', formData)
},
// 更新模型信息
async updateModel(modelId: string, data: { name: string; version: string }): Promise<{ success: boolean; data?: YoloModel; message?: string }> {
return request.put(`/api/yolo/models/${modelId}/update/`, data)
},
// 删除模型
async deleteModel(modelId: string): Promise<{ success: boolean; message?: string }> {
return request.delete(`/api/yolo/models/${modelId}/delete/`)
},
// 启用模型
async enableModel(modelId: string): Promise<{ success: boolean; data?: YoloModel; message?: string }> {
return request.post(`/api/yolo/models/${modelId}/enable/`)
},
// 获取模型详情
async getModelDetail(modelId: string): Promise<{ success: boolean; data?: YoloModel; message?: string }> {
return request.get(`/api/yolo/models/${modelId}/`)
},
// 获取检测历史记录列表
async getDetectionHistory(params?: {
page?: number
page_size?: number
search?: string
start_date?: string
end_date?: string
model_id?: string
}): Promise<{ success: boolean; data?: DetectionHistoryRecord[]; message?: string }> {
return request.get('/api/yolo/detections/', { params })
},
// 获取检测记录详情
async getDetectionDetail(recordId: string): Promise<{ success: boolean; data?: DetectionHistoryRecord; message?: string }> {
return request.get(`/api/detections/${recordId}/`)
},
// 删除检测记录
async deleteDetection(recordId: string): Promise<{ success: boolean; message?: string }> {
return request.delete(`/api/yolo/detections/${recordId}/delete/`)
},
// 批量删除检测记录
async batchDeleteDetections(ids: number[]): Promise<{ success: boolean; message?: string }> {
return request.post('/api/yolo/detections/batch-delete/', { ids })
},
// 获取检测统计
async getDetectionStats(): Promise<{ success: boolean; data?: any; message?: string }> {
return request.get('/api/yolo/stats/')
},
// 数据集管理相关接口
// 上传数据集
async uploadDataset(formData: FormData): Promise<{ success: boolean; data?: YoloDatasetDetail; message?: string }> {
return request.upload('/api/yolo/datasets/upload/', formData)
},
// 获取数据集列表
async getDatasets(): Promise<{ success: boolean; data?: YoloDatasetSummary[]; message?: string }> {
return request.get('/api/yolo/datasets/')
},
// 获取数据集详情
async getDatasetDetail(datasetId: number): Promise<{ success: boolean; data?: YoloDatasetDetail; message?: string }> {
return request.get(`/api/yolo/datasets/${datasetId}/`)
},
// 删除数据集
async deleteDataset(datasetId: number): Promise<{ success: boolean; message?: string }> {
return request.post(`/api/yolo/datasets/${datasetId}/delete/`)
},
// 获取数据集样本
async getDatasetSamples(
datasetId: number,
params: { split?: 'train' | 'val' | 'test'; limit?: number; offset?: number } = {}
): Promise<{
success: boolean
data?: { items: YoloDatasetSampleItem[]; total: number }
message?: string
}> {
return request.get(`/api/yolo/datasets/${datasetId}/samples/`, { params })
},
// YOLO 训练任务相关接口
// 获取训练选项(可用数据集与模型版本)
async getTrainOptions(): Promise<YoloTrainOptionsResponse> {
return request.get('/api/yolo/train/options/')
},
// 获取训练任务列表
async getTrainJobs(): Promise<{
success: boolean
code?: number
message?: string
data?: YoloTrainingJob[]
}> {
return request.get('/api/yolo/train/jobs/')
},
// 创建并启动训练任务
async startTrainJob(payload: StartTrainingPayload): Promise<{
success: boolean
code?: number
message?: string
data?: YoloTrainingJob
}> {
return request.post('/api/yolo/train/jobs/start/', payload)
},
// 获取训练任务详情
async getTrainJobDetail(id: number): Promise<{
success: boolean
code?: number
message?: string
data?: YoloTrainingJob
}> {
return request.get(`/api/yolo/train/jobs/${id}/`)
},
// 获取训练任务日志(分页读取)
async getTrainJobLogs(
id: number,
params: { offset?: number; max?: number } = {}
): Promise<YoloTrainLogsResponse> {
return request.get(`/api/yolo/train/jobs/${id}/logs/`, { params })
},
// 取消训练任务
async cancelTrainJob(id: number): Promise<{
success: boolean
code?: number
message?: string
data?: YoloTrainingJob
}> {
return request.post(`/api/yolo/train/jobs/${id}/cancel/`)
},
// 下载训练结果ZIP
async downloadTrainJobResult(id: number): Promise<{
success: boolean
code?: number
message?: string
data?: { url: string; size: number }
}> {
return request.get(`/api/yolo/train/jobs/${id}/download/`)
},
// 删除训练任务
async deleteTrainJob(id: number): Promise<{
success: boolean
code?: number
message?: string
}> {
return request.post(`/api/yolo/train/jobs/${id}/delete/`)
},
// 警告等级管理相关接口
// 获取警告等级列表
async getAlertLevels(): Promise<{ success: boolean; data?: AlertLevel[]; message?: string }> {
return request.get('/api/yolo/categories/')
},
// 获取警告等级详情
async getAlertLevelDetail(levelId: string): Promise<{ success: boolean; data?: AlertLevel; message?: string }> {
return request.get(`/api/yolo/categories/${levelId}/`)
},
// 更新警告等级
async updateAlertLevel(levelId: string, data: { alert_level?: 'low' | 'medium' | 'high'; alias?: string }): Promise<{ success: boolean; data?: AlertLevel; message?: string }> {
return request.put(`/api/yolo/categories/${levelId}/update/`, data)
},
// 切换警告等级状态
async toggleAlertLevelStatus(levelId: string): Promise<{ success: boolean; data?: AlertLevel; message?: string }> {
return request.post(`/api/yolo/categories/${levelId}/toggle-status/`)
},
// 获取活跃的警告等级列表
async getActiveAlertLevels(): Promise<{ success: boolean; data?: AlertLevel[]; message?: string }> {
return request.get('/api/yolo/categories/active/')
},
// 上传并转换PT模型为ONNX格式
async uploadAndConvertToOnnx(formData: FormData): Promise<{
success: boolean
message?: string
data?: {
onnx_path?: string
onnx_url?: string
download_url?: string
onnx_relative_path?: string
file_name?: string
labels_download_url?: string
labels_relative_path?: string
classes?: string[]
}
}> {
// 适配后端 @views.py 中的 upload_pt_convert_onnx 实现
// 统一走 /api/upload_pt_convert_onnx
// 按你的后端接口:/yolo/onnx/upload/
// 注意带上结尾斜杠,避免 404
return request.upload('/api/yolo/onnx/upload/', formData)
}
}
// 警告等级管理相关接口
export interface AlertLevel {
id: number
model: number
model_name: string
name: string
alias: string
display_name: string
category_id: number
alert_level: 'low' | 'medium' | 'high'
alert_level_display: string
is_active: boolean
// 前端编辑状态字段
editingAlias?: boolean
tempAlias?: string
}
// 用户检测历史相关接口
export interface DetectionHistoryRecord {
id: number
user_id: number
original_filename: string
result_filename: string
original_file: string
result_file: string
detection_type: 'image' | 'video'
object_count: number
detected_categories: string[]
confidence_scores: number[]
avg_confidence: number | null
processing_time: number
model_name: string
model_info: any
created_at: string
confidence_threshold?: number // 置信度阈值(原始设置值)
// 为了兼容前端显示,添加计算字段
filename?: string
image_url?: string
detections?: YoloDetection[]
}
export interface DetectionHistoryParams {
page?: number
page_size?: number
search?: string
class_filter?: string
start_date?: string
end_date?: string
model_id?: string
}
export interface DetectionHistoryResponse {
success?: boolean
message?: string
data?: {
records: DetectionHistoryRecord[]
total: number
page: number
page_size: number
} | DetectionHistoryRecord[]
// 支持直接返回数组的情况
results?: DetectionHistoryRecord[]
count?: number
// 支持Django REST framework的分页格式
next?: string
previous?: string
}
// 用户检测历史API
export const detectionHistoryApi = {
// 获取用户检测历史
async getUserDetectionHistory(userId: number, params: DetectionHistoryParams = {}): Promise<DetectionHistoryResponse> {
return request.get('/api/yolo/detections/', {
params: {
user_id: userId,
...params
}
})
},
// 获取检测记录详情
async getDetectionRecordDetail(recordId: number): Promise<{
success?: boolean
code?: number
message?: string
data?: DetectionHistoryRecord
}> {
return request.get(`/api/yolo/detections/${recordId}/`)
},
// 删除检测记录
async deleteDetectionRecord(userId: number, recordId: string): Promise<{ success: boolean; message?: string }> {
return request.delete(`/api/yolo/detections/${recordId}/delete/`)
},
// 批量删除检测记录
async batchDeleteDetectionRecords(userId: number, recordIds: string[]): Promise<{ success: boolean; message?: string }> {
return request.post('/api/yolo/detections/batch-delete/', { ids: recordIds })
},
// 导出检测历史
async exportDetectionHistory(userId: number, params: DetectionHistoryParams = {}): Promise<Blob> {
const response = await request.get('/api/yolo/detections/export/', {
params: {
user_id: userId,
...params
},
responseType: 'blob'
})
return response
},
// 获取检测统计信息
async getDetectionStats(userId: number): Promise<{
success: boolean
data?: {
total_detections: number
total_images: number
class_counts: Record<string, number>
recent_activity: Array<{
date: string
count: number
}>
}
message?: string
}> {
return request.get('/api/yolo/detections/stats/', {
params: { user_id: userId }
})
}
}
// 告警相关接口类型定义
export interface AlertRecord {
id: number
detection_record: number
detection_info: {
id: number
detection_type: string
original_filename: string
result_filename: string
object_count: number
avg_confidence: number
}
user: number
user_name: string
alert_level: string
alert_level_display: string
alert_category: string
category: number
category_info: {
id: number
name: string
alert_level: string
alert_level_display: string
}
status: string
created_at: string
deleted_at: string | null
}
// 告警管理API
export const alertApi = {
// 获取所有告警记录
async getAllAlerts(): Promise<{ success: boolean; data?: AlertRecord[]; message?: string }> {
return request.get('/api/yolo/alerts/')
},
// 获取当前用户的告警记录
async getUserAlerts(userId: string): Promise<{ success: boolean; data?: AlertRecord[]; message?: string }> {
return request.get(`/api/yolo/users/${userId}/alerts/`)
},
// 处理告警(更新状态)
async updateAlertStatus(alertId: string, status: string): Promise<{ success: boolean; data?: AlertRecord; message?: string }> {
return request.put(`/api/yolo/alerts/${alertId}/update-status/`, { status })
}
}
// 默认导出
export default yoloApi

View File

@@ -0,0 +1,85 @@
export type HertzModuleGroup = 'admin' | 'user'
export interface HertzModule {
key: string
label: string
group: HertzModuleGroup
description?: string
defaultEnabled: boolean
}
export const HERTZ_MODULES: HertzModule[] = [
{ key: 'admin.user-management', label: '管理端 · 用户管理', group: 'admin', defaultEnabled: true },
{ key: 'admin.department-management', label: '管理端 · 部门管理', group: 'admin', defaultEnabled: true },
{ key: 'admin.menu-management', label: '管理端 · 菜单管理', group: 'admin', defaultEnabled: true },
{ key: 'admin.role-management', label: '管理端 · 角色管理', group: 'admin', defaultEnabled: true },
{ key: 'admin.notification-management', label: '管理端 · 通知管理', group: 'admin', defaultEnabled: true },
{ key: 'admin.log-management', label: '管理端 · 日志管理', group: 'admin', defaultEnabled: true },
{ key: 'admin.knowledge-base', label: '管理端 · 文章管理', group: 'admin', defaultEnabled: true },
{ key: 'admin.yolo-model', label: '管理端 · YOLO 模型相关', group: 'admin', defaultEnabled: true },
{ key: 'user.system-monitor', label: '用户端 · 系统监控', group: 'user', defaultEnabled: true },
{ key: 'user.ai-chat', label: '用户端 · AI 助手', group: 'user', defaultEnabled: true },
{ key: 'user.yolo-detection', label: '用户端 · YOLO 检测', group: 'user', defaultEnabled: true },
{ key: 'user.live-detection', label: '用户端 · 实时检测', group: 'user', defaultEnabled: true },
{ key: 'user.detection-history', label: '用户端 · 检测历史', group: 'user', defaultEnabled: true },
{ key: 'user.alert-center', label: '用户端 · 告警中心', group: 'user', defaultEnabled: true },
{ key: 'user.notice-center', label: '用户端 · 通知中心', group: 'user', defaultEnabled: true },
{ key: 'user.knowledge-center', label: '用户端 · 文章中心', group: 'user', defaultEnabled: true },
{ key: 'user.kb-center', label: '用户端 · 知识库中心', group: 'user', defaultEnabled: true },
]
const LOCAL_STORAGE_KEY = 'hertz_enabled_modules'
export function getEnabledModuleKeys(): string[] {
const fallback = HERTZ_MODULES.filter(m => m.defaultEnabled).map(m => m.key)
if (typeof window === 'undefined') {
return fallback
}
try {
const stored = window.localStorage.getItem(LOCAL_STORAGE_KEY)
if (!stored) return fallback
const parsed = JSON.parse(stored)
if (Array.isArray(parsed)) {
const valid = parsed.filter((k): k is string => typeof k === 'string')
// 自动合并新增的默认启用模块,避免新模块在已有选择下被永久隐藏
const missingDefaults = HERTZ_MODULES
.filter(m => m.defaultEnabled && !valid.includes(m.key))
.map(m => m.key)
return [...valid, ...missingDefaults]
}
return fallback
} catch {
return fallback
}
}
export function setEnabledModuleKeys(keys: string[]): void {
if (typeof window === 'undefined') return
try {
window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(keys))
} catch {
// ignore
}
}
export function isModuleEnabled(moduleKey?: string, enabledKeys?: string[]): boolean {
if (!moduleKey) return true
const keys = enabledKeys ?? getEnabledModuleKeys()
return keys.indexOf(moduleKey) !== -1
}
export function getModulesByGroup(group: HertzModuleGroup): HertzModule[] {
return HERTZ_MODULES.filter(m => m.group === group)
}
export function hasModuleSelection(): boolean {
if (typeof window === 'undefined') return false
try {
return window.localStorage.getItem(LOCAL_STORAGE_KEY) !== null
} catch {
return false
}
}

View File

@@ -0,0 +1,159 @@
export default {
common: {
confirm: 'Confirm',
cancel: 'Cancel',
save: 'Save',
delete: 'Delete',
edit: 'Edit',
add: 'Add',
search: 'Search',
reset: 'Reset',
loading: 'Loading...',
noData: 'No Data',
success: 'Success',
error: 'Error',
warning: 'Warning',
info: 'Info',
},
nav: {
home: 'Home',
dashboard: 'Dashboard',
user: 'User Management',
role: 'Role Management',
menu: 'Menu Management',
settings: 'System Settings',
profile: 'Profile',
logout: 'Logout',
},
login: {
title: 'Login',
username: 'Username',
password: 'Password',
login: 'Login',
forgotPassword: 'Forgot Password?',
rememberMe: 'Remember Me',
},
success: {
// General success messages
operationSuccess: 'Operation Successful',
saveSuccess: 'Save Successful',
deleteSuccess: 'Delete Successful',
updateSuccess: 'Update Successful',
// Login and registration related success messages
loginSuccess: 'Login Successful',
registerSuccess: 'Registration Successful! Please Login',
logoutSuccess: 'Logout Successful',
emailCodeSent: 'Verification Code Sent to Your Email',
// User management related success messages
userCreated: 'User Created Successfully',
userUpdated: 'User Information Updated Successfully',
userDeleted: 'User Deleted Successfully',
roleAssigned: 'Role Assigned Successfully',
// Other operation success messages
uploadSuccess: 'File Upload Successful',
downloadSuccess: 'File Download Successful',
copySuccess: 'Copy Successful',
},
error: {
// General errors
// 404: 'Page Not Found',
403: 'Access Denied, Please Contact Administrator',
500: 'Internal Server Error, Please Try Again Later',
networkError: 'Network Connection Failed, Please Check Network Settings',
timeout: 'Request Timeout, Please Try Again Later',
// Login related errors
loginFailed: 'Login Failed, Please Check Username and Password',
usernameRequired: 'Please Enter Username',
passwordRequired: 'Please Enter Password',
captchaRequired: 'Please Enter Captcha',
captchaError: 'Captcha Error, Please Re-enter (Case Sensitive)',
captchaExpired: 'Captcha Expired, Please Refresh and Re-enter',
accountLocked: 'Account Locked, Please Contact Administrator',
accountDisabled: 'Account Disabled, Please Contact Administrator',
passwordExpired: 'Password Expired, Please Change Password',
loginAttemptsExceeded: 'Too Many Login Attempts, Account Temporarily Locked',
// Registration related errors
registerFailed: 'Registration Failed, Please Check Input Information',
usernameExists: 'Username Already Exists, Please Choose Another',
emailExists: 'Email Already Registered, Please Use Another Email',
phoneExists: 'Phone Number Already Registered, Please Use Another',
emailFormatError: 'Invalid Email Format, Please Enter Valid Email',
phoneFormatError: 'Invalid Phone Format, Please Enter 11-digit Phone Number',
passwordTooWeak: 'Password Too Weak, Please Include Uppercase, Lowercase, Numbers and Special Characters',
passwordMismatch: 'Passwords Do Not Match',
emailCodeError: 'Email Verification Code Error or Expired',
emailCodeRequired: 'Please Enter Email Verification Code',
emailCodeLength: 'Verification Code Must Be 6 Digits',
emailRequired: 'Please Enter Email',
usernameLength: 'Username Length Must Be 3-20 Characters',
passwordLength: 'Password Length Must Be 6-20 Characters',
confirmPasswordRequired: 'Please Confirm Password',
phoneRequired: 'Please Enter Phone Number',
realNameRequired: 'Please Enter Real Name',
realNameLength: 'Name Length Must Be 2-10 Characters',
// Permission related errors
accessDenied: 'Access Denied, You Do Not Have Permission to Perform This Action',
roleNotFound: 'Role Not Found or Deleted',
permissionDenied: 'Permission Denied, Cannot Perform This Action',
tokenExpired: 'Login Expired, Please Login Again',
tokenInvalid: 'Invalid Login Status, Please Login Again',
// User management related errors
userNotFound: 'User Not Found or Deleted',
userCreateFailed: 'Failed to Create User, Please Check Input Information',
userUpdateFailed: 'Failed to Update User Information',
userDeleteFailed: 'Failed to Delete User, User May Be In Use',
cannotDeleteSelf: 'Cannot Delete Your Own Account',
cannotDeleteAdmin: 'Cannot Delete Administrator Account',
// Department management related errors
departmentNotFound: 'Department Not Found or Deleted',
departmentNameExists: 'Department Name Already Exists',
departmentHasUsers: 'Department Has Users, Cannot Delete',
departmentCreateFailed: 'Failed to Create Department',
departmentUpdateFailed: 'Failed to Update Department Information',
departmentDeleteFailed: 'Failed to Delete Department',
// Role management related errors
roleNameExists: 'Role Name Already Exists',
roleCreateFailed: 'Failed to Create Role',
roleUpdateFailed: 'Failed to Update Role Information',
roleDeleteFailed: 'Failed to Delete Role',
roleInUse: 'Role In Use, Cannot Delete',
// File upload related errors
fileUploadFailed: 'File Upload Failed',
fileSizeExceeded: 'File Size Exceeded Limit',
fileTypeNotSupported: 'File Type Not Supported',
fileRequired: 'Please Select File to Upload',
// Data validation related errors
invalidInput: 'Invalid Input Data Format',
requiredFieldMissing: 'Required Field Cannot Be Empty',
fieldTooLong: 'Input Content Exceeds Length Limit',
fieldTooShort: 'Input Content Length Insufficient',
invalidDate: 'Invalid Date Format',
invalidNumber: 'Invalid Number Format',
// Operation related errors
operationFailed: 'Operation Failed, Please Try Again Later',
saveSuccess: 'Save Successful',
saveFailed: 'Save Failed, Please Check Input Information',
deleteSuccess: 'Delete Successful',
deleteFailed: 'Delete Failed, Please Try Again Later',
updateSuccess: 'Update Successful',
updateFailed: 'Update Failed, Please Check Input Information',
// System related errors
systemMaintenance: 'System Under Maintenance, Please Visit Later',
serviceUnavailable: 'Service Temporarily Unavailable, Please Try Again Later',
databaseError: 'Database Connection Error, Please Contact Technical Support',
configError: 'System Configuration Error, Please Contact Administrator',
},
}

View File

@@ -0,0 +1,18 @@
import { createI18n } from 'vue-i18n'
import zhCN from './zh-CN'
import enUS from './en-US'
const messages = {
'zh-CN': zhCN,
'en-US': enUS,
}
export const i18n = createI18n({
locale: 'zh-CN',
fallbackLocale: 'en-US',
messages,
legacy: false,
globalInjection: true,
})
export default i18n

View File

@@ -0,0 +1,172 @@
export default {
common: {
confirm: '确定',
cancel: '取消',
save: '保存',
delete: '删除',
edit: '编辑',
add: '添 加',
search: '搜索',
reset: '重置',
loading: '加载中...',
noData: '暂无数据',
success: '成功',
error: '错误',
warning: '警告',
info: '提示',
},
nav: {
home: '首页',
dashboard: '仪表板',
user: '用户管理',
role: '角色管理',
menu: '菜单管理',
settings: '系统设置',
profile: '个人资料',
logout: '退出登录',
},
login: {
title: '登录',
username: '用户名',
password: '密码',
login: '登录',
forgotPassword: '忘记密码?',
rememberMe: '记住我',
},
register: {
title: '注册',
username: '用户名',
email: '邮箱',
password: '密码',
confirmPassword: '确认密码',
register: '注册',
agreement: '我已阅读并同意',
userAgreement: '用户协议',
privacyPolicy: '隐私政策',
hasAccount: '已有账号?',
goToLogin: '立即登录',
},
success: {
// 通用成功提示
operationSuccess: '操作成功',
saveSuccess: '保存成功',
deleteSuccess: '删除成功',
updateSuccess: '更新成功',
// 登录注册相关成功提示
loginSuccess: '登录成功',
registerSuccess: '注册成功!请前往登录',
logoutSuccess: '退出登录成功',
emailCodeSent: '验证码已发送到您的邮箱',
// 用户管理相关成功提示
userCreated: '用户创建成功',
userUpdated: '用户信息更新成功',
userDeleted: '用户删除成功',
roleAssigned: '角色分配成功',
// 其他操作成功提示
uploadSuccess: '文件上传成功',
downloadSuccess: '文件下载成功',
copySuccess: '复制成功',
},
error: {
// 通用错误
// 404: '页面未找到',
403: '权限不足,请联系管理员',
500: '服务器内部错误,请稍后重试',
networkError: '网络连接失败,请检查网络设置',
timeout: '请求超时,请稍后重试',
// 登录相关错误
loginFailed: '登录失败,请检查用户名和密码',
usernameRequired: '请输入用户名',
passwordRequired: '请输入密码',
captchaRequired: '请输入验证码',
captchaError: '验证码错误,请重新输入(区分大小写)',
captchaExpired: '验证码已过期,请刷新后重新输入',
accountLocked: '账户已被锁定,请联系管理员',
accountDisabled: '账户已被禁用,请联系管理员',
passwordExpired: '密码已过期,请修改密码',
loginAttemptsExceeded: '登录尝试次数过多,账户已被临时锁定',
// 注册相关错误
registerFailed: '注册失败,请检查输入信息',
usernameExists: '用户名已存在,请选择其他用户名',
emailExists: '邮箱已被注册,请使用其他邮箱',
phoneExists: '手机号已被注册,请使用其他手机号',
emailFormatError: '邮箱格式不正确,请输入有效的邮箱地址',
phoneFormatError: '手机号格式不正确请输入11位手机号',
passwordTooWeak: '密码强度不足,请包含大小写字母、数字和特殊字符',
passwordMismatch: '两次输入的密码不一致',
emailCodeError: '邮箱验证码错误或已过期',
emailCodeRequired: '请输入邮箱验证码',
emailCodeLength: '验证码长度为6位',
emailRequired: '请输入邮箱',
usernameLength: '用户名长度为3-20个字符',
passwordLength: '密码长度为6-20个字符',
confirmPasswordRequired: '请确认密码',
phoneRequired: '请输入手机号',
realNameRequired: '请输入真实姓名',
realNameLength: '姓名长度为2-10个字符',
// 权限相关错误
accessDenied: '访问被拒绝,您没有执行此操作的权限',
roleNotFound: '角色不存在或已被删除',
permissionDenied: '权限不足,无法执行此操作',
tokenExpired: '登录已过期,请重新登录',
tokenInvalid: '登录状态无效,请重新登录',
// 用户管理相关错误
userNotFound: '用户不存在或已被删除',
userCreateFailed: '创建用户失败,请检查输入信息',
userUpdateFailed: '更新用户信息失败',
userDeleteFailed: '删除用户失败,该用户可能正在使用中',
cannotDeleteSelf: '不能删除自己的账户',
cannotDeleteAdmin: '不能删除管理员账户',
// 部门管理相关错误
departmentNotFound: '部门不存在或已被删除',
departmentNameExists: '部门名称已存在',
departmentHasUsers: '部门下还有用户,无法删除',
departmentCreateFailed: '创建部门失败',
departmentUpdateFailed: '更新部门信息失败',
departmentDeleteFailed: '删除部门失败',
// 角色管理相关错误
roleNameExists: '角色名称已存在',
roleCreateFailed: '创建角色失败',
roleUpdateFailed: '更新角色信息失败',
roleDeleteFailed: '删除角色失败',
roleInUse: '角色正在使用中,无法删除',
// 文件上传相关错误
fileUploadFailed: '文件上传失败',
fileSizeExceeded: '文件大小超出限制',
fileTypeNotSupported: '不支持的文件类型',
fileRequired: '请选择要上传的文件',
// 数据验证相关错误
invalidInput: '输入数据格式不正确',
requiredFieldMissing: '必填字段不能为空',
fieldTooLong: '输入内容超出长度限制',
fieldTooShort: '输入内容长度不足',
invalidDate: '日期格式不正确',
invalidNumber: '数字格式不正确',
// 操作相关错误
operationFailed: '操作失败,请稍后重试',
saveSuccess: '保存成功',
saveFailed: '保存失败,请检查输入信息',
deleteSuccess: '删除成功',
deleteFailed: '删除失败,请稍后重试',
updateSuccess: '更新成功',
updateFailed: '更新失败,请检查输入信息',
// 系统相关错误
systemMaintenance: '系统正在维护中,请稍后访问',
serviceUnavailable: '服务暂时不可用,请稍后重试',
databaseError: '数据库连接错误,请联系技术支持',
configError: '系统配置错误,请联系管理员',
},
}

View File

@@ -0,0 +1,47 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import { i18n } from './locales'
import { checkEnvironmentVariables, validateEnvironment } from './utils/hertz_env'
import './styles/index.scss'
// 导入Ant Design Vue
import 'ant-design-vue/dist/antd.css'
// 开发环境检查
if (import.meta.env.DEV) {
checkEnvironmentVariables()
validateEnvironment()
}
// 创建Vue应用实例
const app = createApp(App)
// 使用Pinia状态管理
const pinia = createPinia()
app.use(pinia)
// 使用路由
app.use(router)
// 使用国际化
app.use(i18n)
// 初始化应用设置
import { useAppStore } from './stores/hertz_app'
const appStore = useAppStore()
appStore.initAppSettings()
// 检查用户认证状态
import { useUserStore } from './stores/hertz_user'
const userStore = useUserStore()
userStore.checkAuth()
// 初始化主题(必须在挂载前加载)
import { useThemeStore } from './stores/hertz_theme'
const themeStore = useThemeStore()
themeStore.loadTheme()
// 挂载应用
app.mount('#app')

Some files were not shown because too many files have changed in this diff Show More