优化更新
This commit is contained in:
Binary file not shown.
@@ -3,7 +3,7 @@ import type { DefaultTheme, LocaleSpecificConfig } from "vitepress";
|
||||
export const META_URL = "https://www.djangoadmin.cn";
|
||||
export const META_TITLE = "DjangoAdmin 企业级中台解决方案";
|
||||
export const META_DESCRIPTION =
|
||||
"DjangoAdmin 企业级中台解决方案 - Django + ElementPlus 前后端分离版本";
|
||||
"DjangoAdmin 企业级中台解决方案 - Django + AntDesign 前后端分离版本";
|
||||
|
||||
export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
description: META_DESCRIPTION,
|
||||
@@ -30,7 +30,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
|
||||
themeConfig: {
|
||||
editLink: {
|
||||
pattern: "https://gitee.com/djangoadmin/DjangoAdmin_Django_EleVue",
|
||||
pattern: "https://gitee.com/djangoadmin/DjangoAdmin_Django_AntdVue",
|
||||
text: "对本页提出修改建议",
|
||||
},
|
||||
|
||||
@@ -197,7 +197,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
},
|
||||
{
|
||||
text: "Gitee 仓库",
|
||||
link: "https://gitee.com/djangoadmin/DjangoAdmin_Django_EleVue",
|
||||
link: "https://gitee.com/djangoadmin/DjangoAdmin_Django_AntdVue",
|
||||
},
|
||||
{
|
||||
text: "Django 文档",
|
||||
@@ -208,8 +208,8 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
link: "https://cn.vuejs.org/",
|
||||
},
|
||||
{
|
||||
text: "ElementPlus",
|
||||
link: "https://element-plus.org/zh-CN/",
|
||||
text: "Ant Design Vue",
|
||||
link: "https://antdv.com/components/overview",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -779,10 +779,10 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
},
|
||||
|
||||
// ================================================================
|
||||
// 第 9 章:其他
|
||||
// 第 9 章:附录
|
||||
// ================================================================
|
||||
{
|
||||
text: "📄 9. 其他",
|
||||
text: "📄 附录",
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: "9.1 常见问题汇总", link: "/zh/faq.html" },
|
||||
@@ -790,7 +790,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
{ text: "9.3 免责声明", link: "/zh/other/declare.html" },
|
||||
{
|
||||
text: "9.4 开源协议",
|
||||
link: "https://gitee.com/djangoadmin/DjangoAdmin_Django_EleVue/blob/master/LICENSE",
|
||||
link: "https://gitee.com/djangoadmin/DjangoAdmin_Django_AntdVue/blob/master/LICENSE",
|
||||
},
|
||||
{ text: "9.5 广告赞助", link: "/zh/other/sponsor.html" },
|
||||
],
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
layout: home
|
||||
|
||||
title: DjangoAdmin文档
|
||||
titleTemplate: 前后端分离Django+ElementPlus版
|
||||
titleTemplate: 前后端分离Django+AntDesign版
|
||||
|
||||
hero:
|
||||
name: DjangoAdmin
|
||||
text: "企业级中台解决方案Django+ElementPlus版"
|
||||
text: "企业级中台解决方案Django+AntDesign版"
|
||||
tagline: "赋能开发者,助力企业发展。\n企业级数据平台底座 · 让数字化触手可及。"
|
||||
image:
|
||||
src: /logo.svg
|
||||
@@ -17,7 +17,7 @@ hero:
|
||||
link: /zh/introduction
|
||||
- theme: alt
|
||||
text: 在线演示
|
||||
link: http://manage.django.elevue.djangoadmin.cn
|
||||
link: http://manage.django.antdvue.djangoadmin.cn
|
||||
- theme: alt
|
||||
text: 官方授权
|
||||
link: https://www.djangoadmin.cn/goods/detail/2
|
||||
|
||||
@@ -12,15 +12,15 @@
|
||||
| 2.1.0 | 2024-06-23 | RBAC 权限体系 + JWT 认证 + 验证码 |
|
||||
| 2.2.0 | 2024-09-06 | 字典管理 + 系统配置 + 参数管理 |
|
||||
| 2.3.0 | 2024-12-19 | 文章管理 + 分类管理 + 文件上传 + XSS 防护 |
|
||||
| 2.4.0 | 2025-03-11 | Vue3 + ElementPlus + TypeScript + Vite |
|
||||
| 2.4.0 | 2025-03-11 | Vue3 + Ant Design Vue + TypeScript + Vite |
|
||||
| 2.5.0 | 2025-06-25 | 代码生成器 CLI + Web UI + Excel 导入导出 |
|
||||
| 2.6.0 | 2025-09-17 | 定时任务 + 操作日志 + 登录日志 |
|
||||
| 2.7.0 | 2025-12-08 | 接口限流 + IP 地理位置 + 安全防护 |
|
||||
| 2.8.0 | 2026-03-22 | Docker 容器化 + Nginx + Supervisor |
|
||||
| 2.9.0 | 2026-06-15 | 文档体系 + 缓存优化 + 性能提升 |
|
||||
| **3.0.0** | **2026-09-01** | **Django 6.0 + Python 3.12 + 架构优化** |
|
||||
| **3.0.0** | **2026-09-10** | **Django 6.0 + Python 3.12 + 架构优化** |
|
||||
|
||||
## [3.0.0] - 2026-09-01
|
||||
## [3.0.0] - 2026-09-10
|
||||
|
||||
**Django 6.0 升级,技术栈全面焕新。**
|
||||
|
||||
@@ -172,7 +172,7 @@
|
||||
### 新增
|
||||
|
||||
- **Vue3 升级**:前端框架从 Vue 2 升级至 Vue3.4+,全面拥抱 Composition API
|
||||
- **ElementPlus**:采用 ElementPlus 组件库,适配 Vue3 生态
|
||||
- **Ant Design Vue**:采用 Ant Design Vue 组件库,适配 Vue3 生态
|
||||
- **Pinia 状态管理**:替代 Vuex,更轻量、更灵活、TypeScript 支持更完善
|
||||
- **TypeScript 类型安全**:全量类型定义,IDE 智能提示与编译时类型检查
|
||||
- **Vite 构建工具**:替代 Webpack,开发服务器毫秒级启动,HMR 即时生效
|
||||
@@ -274,7 +274,7 @@
|
||||
- **Django Web 框架**:基于 Django 5.x,成熟的 MTV 架构,丰富的生态系统
|
||||
- **Django ORM**:声明式模型定义,自动迁移,多数据库方言支持
|
||||
- **Django ModelForm**:声明式表单验证,自动错误信息生成
|
||||
- **Vue3 前端**:基于 Vue3 + ElementPlus,前后端分离架构
|
||||
- **Vue3 前端**:基于 Vue3 + Ant Design Vue,前后端分离架构
|
||||
- **用户管理**:用户账号管理、密码重置、启用/停用状态控制
|
||||
- **角色管理**:角色 CRUD、权限节点配置
|
||||
- **菜单管理**:菜单 CRUD、图标设置、动态路由生成
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
```bash
|
||||
# 进入项目目录(以实际解压路径为准)
|
||||
cd DjangoAdmin_Django_EleVue
|
||||
cd DjangoAdmin_Django_AntdVue
|
||||
```
|
||||
|
||||
:::tip 温馨提示
|
||||
@@ -112,7 +112,7 @@ REDIS_DB=0
|
||||
DJANGO_FILE_URL=http://file.django.elevue
|
||||
```
|
||||
|
||||
:::danger
|
||||
:::danger 重要提示
|
||||
`DATABASE_NAME` 需要提前在 MySQL 中创建对应的数据库:
|
||||
```sql
|
||||
CREATE DATABASE `djangoadmin.django.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
## 软件说明
|
||||
|
||||
基于 Django、Django ORM、Vue3、TypeScript、Vite、ElementPlus、MySQL 等技术栈构建的企业级前后端分离后台管理系统。
|
||||
基于 Django、Django ORM、Vue3、TypeScript、Vite、Ant Design Vue 等技术栈构建的企业级前后端分离后台管理系统,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库。
|
||||
|
||||
后端采用 Python 语言,基于 Django Web 框架、Django ORM 数据库操作、Django Form 数据校验等主流技术栈;前端基于 Vue3、TypeScript、Vite 构建,采用 ElementPlus 框架,遵循 Vue 前后端分离主流设计思想。
|
||||
后端采用 Python 语言,基于 Django Web 框架、Django ORM 数据库操作、Django Form 数据校验等主流技术栈;前端基于 Vue3、TypeScript、Vite 构建,采用 Ant Design Vue 框架,遵循 Vue 前后端分离主流设计思想。
|
||||
|
||||
系统采用完全前后端分离架构,前端通过 API 调用后端服务实现数据交互。内置完善的 RBAC 权限体系,权限颗粒度精细至按钮级别;支持多主题切换,兼容桌面端和平板端。系统预置了用户、角色、菜单、部门、岗位、职级、日志、字典、配置、参数、定时任务、代码生成器等基础功能模块,开箱即用。
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
+ **分层架构**:Model(数据)→ Form(验证)→ Service(业务)→ View(视图)→ URL(路由),职责清晰
|
||||
+ **基类复用**:BaseModel 提供 6 个公共字段(id、创建人、时间、软删除),子类只需声明业务字段
|
||||
+ **模块可插拔**:业务模块位于 `application/`,按 Django App 机制注册,增删不影响核心框架
|
||||
+ **多数据库支持**:通过 `DATABASE_ENGINE` 环境变量一键切换 MySQL / PostgreSQL / SQLite / Oracle
|
||||
+ **多数据库支持**:通过 `DATABASE_ENGINE` 环境变量一键切换 MySQL / PostgreSQL / SQLite / Oracle / SQL Server 等数据库
|
||||
+ **中间件可扩展**:CORS、认证、权限、日志通过 Django 中间件链式处理,按需注册
|
||||
+ **RBAC 权限**:后端 PermissionRequired + 前端 v-perm 指令双重校验,精确到按钮级
|
||||
+ **代码生成器**:根据数据库表结构一键生成前后端 CRUD 代码,含列表、表单、API 全套
|
||||
@@ -42,13 +42,17 @@
|
||||
+ **通知公告**:主要对通知、公告等站内数据进行管理
|
||||
+ **行政区划**:针对全国行政区划分数据进行管理和维护
|
||||
+ **文件模板**:主要对系统中使用的文件模板进行上传和管理
|
||||
+ **文件上传**:提供统一的文件上传服务,支持图片、文档等多类型文件管理,自动重命名、按日期分目录存储
|
||||
+ **友情链接**:用于管理外部链接和合作伙伴链接数据,支持分类管理和排序
|
||||
+ **文章管理**:用于发布和管理文章内容,支持富文本编辑、分类管理和状态管理
|
||||
+ **任务调度**:主要用于对定时任务进行定义和统一调度管理
|
||||
+ **代码生成**:根据数据库表结构一键生成前后端 CRUD 代码,含列表、表单、API 全套
|
||||
+ **分类管理**:用于文章分类、商品分类等树形分类数据的管理
|
||||
+ **示例模块**:提供代码生成器的示例参考,展示标准 CRUD 模块的实现方式
|
||||
|
||||
## 软件出处
|
||||
|
||||
+ 软件名称:小蚂蚁云前后端分离Django+ElementPlus版本
|
||||
+ 软件名称:小蚂蚁云前后端分离Django+AntDesign版本
|
||||
+ 软件作者:@小蚂蚁云团队
|
||||
+ 软件协议:Apache-2.0
|
||||
+ 官网网址:[https://www.djangoadmin.cn](https://www.djangoadmin.cn)
|
||||
@@ -59,82 +63,84 @@
|
||||
|
||||
| 版本名称 | 说明 | 地址 |
|
||||
|:---------|:-----|:-----|
|
||||
| Django+Layui混编版 | 基于 Django、Jinja2、Layui、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_Layui) |
|
||||
| Django+EleVue前后端分离版 | 基于 Django、Django ORM、Vue3、ElementPlus、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_EleVue) |
|
||||
| Django+AntdVue前后端分离版 | 基于 Django、Django ORM、Vue3、AntDesign、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_AntdVue) |
|
||||
| Django+NaiveVue前后端分离版 | 基于 Django、Django ORM、Vue3、NaiveUI、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_NaiveVue) |
|
||||
| Flask+Layui混编版 | 基于 Flask、Jinja2、Layui、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_Layui) |
|
||||
| Flask+EleVue前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_EleVue) |
|
||||
| Flask+AntdVue前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_AntdVue) |
|
||||
| Flask+NaiveVue前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_NaiveVue) |
|
||||
| FastAPI+Layui混编版 | 基于 FastAPI、Jinja2、Layui、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_Layui) |
|
||||
| FastAPI+EleVue前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_EleVue) |
|
||||
| FastAPI+AntdVue前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_AntdVue) |
|
||||
| FastAPI+NaiveVue前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_NaiveVue) |
|
||||
| Tornado+Layui混编版 | 基于 Tornado、Jinja2、Layui、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_Layui) |
|
||||
| Tornado+EleVue前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_EleVue) |
|
||||
| Tornado+AntdVue前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_AntdVue) |
|
||||
| Tornado+NaiveVue前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_NaiveVue) |
|
||||
| Django+Layui混编版 | 基于 Django、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_Layui) |
|
||||
| Django+ElementPlus前后端分离版 | 基于 Django、Django ORM、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_EleVue) |
|
||||
| Django+AntDesign前后端分离版 | 基于 Django、Django ORM、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_AntdVue) |
|
||||
| Django+NaiveUI前后端分离版 | 基于 Django、Django ORM、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_NaiveVue) |
|
||||
| Flask+Layui混编版 | 基于 Flask、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_Layui) |
|
||||
| Flask+ElementPlus前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_EleVue) |
|
||||
| Flask+AntDesign前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_AntdVue) |
|
||||
| Flask+NaiveUI前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_NaiveVue) |
|
||||
| FastAPI+Layui混编版 | 基于 FastAPI、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_Layui) |
|
||||
| FastAPI+ElementPlus前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_EleVue) |
|
||||
| FastAPI+AntDesign前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_AntdVue) |
|
||||
| FastAPI+NaiveUI前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_NaiveVue) |
|
||||
| Tornado+Layui混编版 | 基于 Tornado、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_Layui) |
|
||||
| Tornado+ElementPlus前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_EleVue) |
|
||||
| Tornado+AntDesign前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_AntdVue) |
|
||||
| Tornado+NaiveUI前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_NaiveVue) |
|
||||
|
||||
## 模块展示
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="/demo/1.png"/></td>
|
||||
<td><img src="/demo/2.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/3.png"/></td>
|
||||
<td><img src="/demo/4.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/5.png"/></td>
|
||||
<td><img src="/demo/6.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/7.png"/></td>
|
||||
<td><img src="/demo/8.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/9.png"/></td>
|
||||
<td><img src="/demo/10.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/11.png"/></td>
|
||||
<td><img src="/demo/12.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/13.png"/></td>
|
||||
<td><img src="/demo/14.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/15.png"/></td>
|
||||
<td><img src="/demo/16.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/17.png"/></td>
|
||||
<td><img src="/demo/18.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/19.png"/></td>
|
||||
<td><img src="/demo/20.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/21.png"/></td>
|
||||
<td><img src="/demo/22.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/23.png"/></td>
|
||||
<td><img src="/demo/24.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/25.png"/></td>
|
||||
<td><img src="/demo/26.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/27.png"/></td>
|
||||
<td><img src="/demo/28.png"/></td>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><img src="/demo/1.png"/></td>
|
||||
<td><img src="/demo/2.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/3.png"/></td>
|
||||
<td><img src="/demo/4.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/5.png"/></td>
|
||||
<td><img src="/demo/6.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/7.png"/></td>
|
||||
<td><img src="/demo/8.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/9.png"/></td>
|
||||
<td><img src="/demo/10.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/11.png"/></td>
|
||||
<td><img src="/demo/12.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/13.png"/></td>
|
||||
<td><img src="/demo/14.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/15.png"/></td>
|
||||
<td><img src="/demo/16.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/17.png"/></td>
|
||||
<td><img src="/demo/18.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/19.png"/></td>
|
||||
<td><img src="/demo/20.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/21.png"/></td>
|
||||
<td><img src="/demo/22.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/23.png"/></td>
|
||||
<td><img src="/demo/24.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/25.png"/></td>
|
||||
<td><img src="/demo/26.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/27.png"/></td>
|
||||
<td><img src="/demo/28.png"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## 开源协议
|
||||
@@ -145,4 +151,3 @@
|
||||
## 版权信息
|
||||
|
||||
版权所有 Copyright © 2021~2026 [djangoadmin.cn](https://www.djangoadmin.cn) All rights reserved。
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
## 开发环境搭建
|
||||
|
||||
1. Fork 项目到自己的 Gitee/GitHub 账号
|
||||
2. 克隆到本地:`git clone https://gitee.com/yourname/DjangoAdmin_Django_EleVue.git`
|
||||
2. 克隆到本地:`git clone https://gitee.com/yourname/DjangoAdmin_Django_AntdVue.git`
|
||||
3. 参考 [开发环境准备](/zh/volume1/environment.html) 安装依赖
|
||||
4. 创建开发分支:`git checkout -b feature/your-feature`
|
||||
|
||||
@@ -60,7 +60,7 @@ feat(generator): 支持树形表格模板生成
|
||||
|
||||
- 新增 ui2/ 树形模板目录
|
||||
- 自动识别 parent_id/pid 字段
|
||||
- 支持 el-tree-select 组件
|
||||
- 支持 a-tree-select 组件
|
||||
|
||||
Closes #123
|
||||
```
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
## 整体结构
|
||||
|
||||
```
|
||||
DjangoAdmin_Django_EleVue/
|
||||
├── application/ # Django 应用目录(所有业务模块)
|
||||
│ ├── example/ # 示例模块(标准 CRUD 模板,可作为开发参考)
|
||||
│ │ ├── models.py # 数据模型
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
```bash
|
||||
# 进入项目根目录(以实际解压路径为准
|
||||
cd DjangoAdmin_Django_EleVue
|
||||
cd DjangoAdmin_Django_AntdVue
|
||||
```
|
||||
|
||||
:::tip 温馨提示
|
||||
@@ -204,14 +204,7 @@ python manage.py migrate
|
||||
`makemigrations` 会根据 `models.py` 中的模型定义自动生成迁移文件,`migrate` 会执行迁移文件创建或修改数据表结构。更多数据库相关内容请参见 [数据库初始化](/zh/volume1/database) 章节。
|
||||
:::
|
||||
|
||||
### 第三步:回填数据库字段注释(可选)
|
||||
|
||||
```bash
|
||||
# 为数据库表和字段添加中文注释
|
||||
python manage.py add_full_comments
|
||||
```
|
||||
|
||||
### 第四步:导入初始数据
|
||||
### 第三步:导入初始数据
|
||||
|
||||
数据库初始数据(管理员账号、默认角色、菜单权限等)通过 SQL 脚本导入。脚本位于项目对应目录中:
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ DATABASE_PREFIX="django_" # 数据表前缀
|
||||
| MySQL | `django.db.backends.mysql` | 3306 | mysqlclient |
|
||||
| PostgreSQL | `django.db.backends.postgresql` | 5432 | psycopg2-binary |
|
||||
| SQLite | `django.db.backends.sqlite3` | - | 内置 |
|
||||
| Oracle | `django.db.backends.oracle` | 1521 | cx-Oracle |
|
||||
| Oracle | `django.db.backends.oracle` | 1521 | oracledb |
|
||||
| SQL Server | `mssql` | 1433 | mssql-django |
|
||||
|
||||
切换数据库引擎后,需安装对应的 Python 驱动包。例如切换到 PostgreSQL:
|
||||
@@ -122,20 +122,7 @@ mysql -uroot -p djangoadmin.django.elevue < document/mysql/djangoadmin.django.el
|
||||
| 系统配置 | 系统默认配置项 |
|
||||
:::
|
||||
|
||||
## 第五步:回填数据库字段注释(可选)
|
||||
|
||||
项目提供了自定义管理命令,用于为数据库表和字段添加中文注释:
|
||||
|
||||
```bash
|
||||
# 为所有数据表添加字段注释
|
||||
python manage.py add_full_comments
|
||||
```
|
||||
|
||||
:::tip 温馨提示
|
||||
此命令会读取 Django 模型中的 `verbose_name` 和 `help_text` 属性,将其写入数据库的字段注释中,便于使用 Navicat 等工具查看表结构时理解每个字段的含义。
|
||||
:::
|
||||
|
||||
## 第六步:验证
|
||||
## 第五步:验证
|
||||
|
||||
启动后端服务,验证数据库连接是否正常:
|
||||
|
||||
@@ -271,7 +258,6 @@ cp db_backup.sqlite3 db.sqlite3
|
||||
2. 配置 .env:设置 DATABASE_ENGINE、DATABASE_NAME 等连接信息
|
||||
3. 执行迁移:python manage.py makemigrations && python manage.py migrate
|
||||
4. 导入初始数据:mysql ... < document/mysql/djangoadmin.django.elevue.sql
|
||||
5. 回填注释(可选):python manage.py add_full_comments
|
||||
```
|
||||
|
||||
后续的模型变更通过 Django 迁移系统管理(`makemigrations` + `migrate`),确保数据库结构与代码保持同步。如需使用代码生成器创建新模块,生成器会自动处理数据库迁移。
|
||||
|
||||
@@ -146,8 +146,8 @@ DJANGO_NAME=Django+EleVue旗舰版
|
||||
DJANGO_VERSION=v3.0.0
|
||||
DJANGO_DEBUG=True
|
||||
DJANGO_DEMO=False # 代码默认True(演示模式),开发环境设为False以启用写操作
|
||||
DJANGO_UPLOAD_DIR="E:\DjangoAdmin_V3\DjangoAdmin_Django_EleVue\public\uploads"
|
||||
DJANGO_FILE_URL='http://file.django.elevue'
|
||||
DJANGO_UPLOAD_DIR="E:\DjangoAdmin_Django_AntdVue\public\uploads"
|
||||
DJANGO_FILE_URL='http://file.django.antdvue'
|
||||
|
||||
# ============================================================
|
||||
# 数据库配置
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 环境准备
|
||||
|
||||
本章节介绍在本地搭建项目所需的基础环境和软件安装。项目采用 `Python` + `Django` 后端、`Vue3` + `ElementPlus` 前端的分离架构,本地开发需准备以下软件。
|
||||
本章节介绍在本地搭建项目所需的基础环境和软件安装。项目采用 `Python` + `Django` 后端、`Vue3` + `Ant Design Vue` 前端的分离架构,本地开发需准备以下软件。
|
||||
|
||||
安装软件:`Python(3.12+)`、`MySQL(8.0+)`、`Redis(6.0+)`、`Node.js(18+)`、`pnpm` 等。
|
||||
安装软件:`Python(3.12+)`、`MySQL(8.0+)`(或其他支持的数据库)、`Redis(6.0+)`、`Node.js(18+)`、`pnpm` 等。
|
||||
|
||||
:::tip 温馨提示
|
||||
1. 部分软件安装后需要设置本地环境变量,便于在终端中使用命令。
|
||||
@@ -192,7 +192,7 @@ OK
|
||||
|
||||
## 安装 Node.js
|
||||
|
||||
`Node.js` 是前端项目的运行环境,要求 `18` 或更高版本。前端基于 `Vue3` + `ElementPlus` 构建。
|
||||
`Node.js` 是前端项目的运行环境,要求 `18` 或更高版本。前端基于 `Vue3` + `Ant Design Vue` 构建。
|
||||
|
||||
+ 下载 Node.js
|
||||
|
||||
|
||||
@@ -378,8 +378,8 @@ python manage.py shell
|
||||
|
||||
```bash
|
||||
# .env 中的上传配置
|
||||
DJANGO_UPLOAD_DIR="E:\DjangoAdmin_V3\DjangoAdmin_Django_EleVue\public\uploads"
|
||||
DJANGO_FILE_URL='http://file.django.elevue'
|
||||
DJANGO_UPLOAD_DIR="E:\DjangoAdmin_Django_AntdVue\public\uploads"
|
||||
DJANGO_FILE_URL='http://file.django.antdvue'
|
||||
```
|
||||
|
||||
:::tip 温馨提示
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 前端启动
|
||||
|
||||
本章节介绍如何将前端项目安装依赖并启动开发服务器。前端基于 `Vue3` + `ElementPlus` + `TypeScript` + `Vite` 构建,采用组件化开发模式,支持热更新和代理配置。
|
||||
本章节介绍如何将前端项目安装依赖并启动开发服务器。前端基于 `Vue3` + `Ant Design Vue` + `TypeScript` + `Vite` 构建,采用组件化开发模式,支持热更新和代理配置。
|
||||
|
||||
:::tip 温馨提示
|
||||
启动前端前,请确保已完成 [环境准备](/zh/volume1/environment) 章节中的 `Node.js` 和 `pnpm` 安装,并已启动后端服务(参见 [后端启动](/zh/volume1/backend))。
|
||||
|
||||
@@ -162,7 +162,7 @@ avatar = save_file(avatar_url, "example")
|
||||
|
||||
```vue
|
||||
<!-- 权限标识必须与后端一致 -->
|
||||
<el-button v-perm="['sys:example:add']">添加案例</el-button>
|
||||
<a-button v-perm="['sys:example:add']">添加案例</a-button>
|
||||
```
|
||||
|
||||
### Q13: 表格数据不刷新
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 前端页面视图
|
||||
|
||||
前端页面视图是用户直接交互的界面。项目使用 Vue3 + ElementPlus + Vite 构建。一个完整的 CRUD 页面由 5 个文件组成:
|
||||
前端页面视图是用户直接交互的界面。项目使用 Vue3 + Ant Design Vue + Vite 构建。一个完整的 CRUD 页面由 5 个文件组成:
|
||||
|
||||
```
|
||||
ui/src/views/tool/example/
|
||||
@@ -21,30 +21,52 @@ ui/src/views/tool/example/
|
||||
<template>
|
||||
<PageWrapper>
|
||||
<!-- 搜索表单卡片 -->
|
||||
<el-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||
<a-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" />
|
||||
</el-card>
|
||||
</a-card>
|
||||
|
||||
<!-- 数据表格卡片 -->
|
||||
<el-card :bordered="false" class="proCard">
|
||||
<a-card :bordered="false" class="proCard">
|
||||
<BasicTable
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="tableRef"
|
||||
:actionColumn="actionColumn"
|
||||
@selection-change="onSelectionChange"
|
||||
:row-selection="{ onChange: onSelectionChange }"
|
||||
scroll-x="1200"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<el-button type="primary" @click="handleAdd" v-perm="['sys:example:add']">
|
||||
添加案例
|
||||
</el-button>
|
||||
<el-button type="danger" @click="handleDelete()" :disabled="!selectionData.length">
|
||||
批量删除
|
||||
</el-button>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleAdd" v-perm="['sys:example:add']">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
添加案例
|
||||
</a-button>
|
||||
<a-button type="danger" @click="handleDelete()" :disabled="!selectionData.length" v-perm="['sys:example:batchDelete']">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
批量删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleEdit(record)" v-perm="['sys:example:update']">
|
||||
<template #icon><EditOutlined /></template>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="primary" @click="handleDetail(record)" v-perm="['sys:example:detail']">
|
||||
<template #icon><EyeOutlined /></template>
|
||||
详情
|
||||
</a-button>
|
||||
<a-button type="primary" danger @click="handleDelete(record)" v-perm="['sys:example:delete']">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</el-card>
|
||||
</a-card>
|
||||
|
||||
<!-- 编辑对话框 -->
|
||||
<editDialog v-if="editVisible" :id="currentId" v-model:visible="editVisible" @success="reloadTable('noRefresh')" />
|
||||
@@ -58,12 +80,13 @@ ui/src/views/tool/example/
|
||||
|
||||
```vue
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, h, nextTick, defineAsyncComponent } from 'vue';
|
||||
import { reactive, ref, nextTick, defineAsyncComponent } from 'vue';
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons-vue';
|
||||
import { schemas } from './querySchemas';
|
||||
import { useForm } from '@/components/Form/index';
|
||||
import { TableAction } from '@/components/Table';
|
||||
import { getExamplePage, exampleDelete, exampleBatchDelete } from '@/api/tool/example';
|
||||
import { columns } from './columns';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
|
||||
// 动态导入弹窗组件
|
||||
const editDialog = defineAsyncComponent(() => import('./edit.vue'));
|
||||
@@ -79,24 +102,6 @@ ui/src/views/tool/example/
|
||||
// 查询参数
|
||||
const formParams = reactive({ name: '', type: '', status: '' });
|
||||
|
||||
// 操作栏配置
|
||||
const actionColumn = reactive({
|
||||
width: 300,
|
||||
label: '操作',
|
||||
prop: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{ label: '编辑', icon: 'Edit', type: 'warning', onClick: handleEdit.bind(null, record), auth: ['sys:example:update'] },
|
||||
{ label: '详情', icon: 'View', type: '', onClick: handleDetail.bind(null, record), auth: ['sys:example:detail'] },
|
||||
{ label: '删除', icon: 'Delete', type: 'danger', onClick: handleDelete.bind(null, record), auth: ['sys:example:delete'] },
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// 加载数据
|
||||
const loadDataTable = async (params: any) => {
|
||||
return await getExamplePage({ ...formParams, ...params });
|
||||
@@ -104,10 +109,15 @@ ui/src/views/tool/example/
|
||||
|
||||
// 注册搜索表单
|
||||
const [register, {}] = useForm({
|
||||
labelWidth: 80,
|
||||
layout: 'horizontal',
|
||||
colProps: { span: 6 },
|
||||
submitOnReset: true,
|
||||
rowProps: { gutter: [16, 0] },
|
||||
colProps: {
|
||||
xs: 24,
|
||||
sm: 24,
|
||||
md: 12,
|
||||
lg: 8,
|
||||
xl: 6,
|
||||
},
|
||||
labelCol: { span: 6, offset: 0 },
|
||||
schemas,
|
||||
});
|
||||
|
||||
@@ -127,22 +137,27 @@ ui/src/views/tool/example/
|
||||
const handleAdd = async () => { currentId.value = 0; await nextTick(); editVisible.value = true; };
|
||||
|
||||
// 编辑
|
||||
const handleEdit = async (record: Recordable) => { currentId.value = record.row.id; await nextTick(); editVisible.value = true; };
|
||||
const handleEdit = async (record: Recordable) => { currentId.value = record.id; await nextTick(); editVisible.value = true; };
|
||||
|
||||
// 详情
|
||||
const handleDetail = async (record: Recordable) => { currentId.value = record.row.id; await nextTick(); detailVisible.value = true; };
|
||||
const handleDetail = async (record: Recordable) => { currentId.value = record.id; await nextTick(); detailVisible.value = true; };
|
||||
|
||||
// 删除
|
||||
async function handleDelete(record: Recordable) {
|
||||
let ids = [];
|
||||
if (!record) {
|
||||
ids = selectionData.value.map(({ id }) => id);
|
||||
if (!ids.length) { message('请选择要删除的数据', 'warning'); return; }
|
||||
if (!ids.length) { message.warning('请选择要删除的数据'); return; }
|
||||
}
|
||||
await confirm('确定要删除选中的案例吗?');
|
||||
record ? await exampleDelete(record.row.id) : await exampleBatchDelete(ids);
|
||||
message('删除成功');
|
||||
reloadTable();
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的案例吗?',
|
||||
onOk: async () => {
|
||||
record ? await exampleDelete(record.id) : await exampleBatchDelete(ids);
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
```
|
||||
@@ -152,8 +167,9 @@ ui/src/views/tool/example/
|
||||
- **PageWrapper**:页面容器,提供统一的页面布局
|
||||
- **BasicForm**:搜索表单组件,通过 `useForm` 注册
|
||||
- **BasicTable**:数据表格组件,内置分页功能
|
||||
- **TableAction**:操作栏组件,支持权限控制
|
||||
- **bodyCell 插槽**:操作列通过 `bodyCell` 插槽 + `v-perm` 指令实现权限控制
|
||||
- **defineAsyncComponent**:懒加载弹窗组件,优化首屏加载
|
||||
- **Modal.confirm**:Ant Design Vue 的确认弹窗
|
||||
- **v-perm**:权限指令,控制按钮显示
|
||||
|
||||
## 2. 编辑弹窗 edit.vue
|
||||
@@ -164,40 +180,42 @@ ui/src/views/tool/example/
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="props.visible"
|
||||
<a-modal
|
||||
v-model:visible="props.visible"
|
||||
:title="props.id ? '编辑案例' : '新增案例'"
|
||||
width="500"
|
||||
:close-on-click-modal="false"
|
||||
:before-close="dialogClose"
|
||||
width="500px"
|
||||
@cancel="dialogClose"
|
||||
:mask-closable="false"
|
||||
>
|
||||
<el-form class="ls-form" ref="formRef" :model="formData" label-width="80px">
|
||||
<el-form-item label="案例名称" prop="name" :rules="{ required: true, message: '请输入案例名称', trigger: 'blur' }">
|
||||
<el-input v-model="formData.name" placeholder="请输入案例名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="案例图片" prop="avatar">
|
||||
<a-form class="ls-form" ref="formRef" :model="formData" :label-col="{ style: { width: '80px' } }">
|
||||
<a-form-item label="案例名称" name="name" :rules="{ required: true, message: '请输入案例名称', trigger: 'blur' }">
|
||||
<a-input v-model:value="formData.name" placeholder="请输入案例名称" allow-clear />
|
||||
</a-form-item>
|
||||
<a-form-item label="案例图片" name="avatar">
|
||||
<UploadImg v-model:image-url="formData.avatar" name="example" />
|
||||
</el-form-item>
|
||||
<el-form-item label="案例类型" prop="type" :rules="{ required: true, message: '请选择案例类型', trigger: 'change' }">
|
||||
<el-select v-model="formData.type" placeholder="请选择" clearable filterable>
|
||||
<el-option v-for="item in optionsData.typeList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="案例状态" prop="status" :rules="{ required: true, message: '请选择状态', trigger: 'change' }">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :value="1">正常</el-radio>
|
||||
<el-radio :value="2">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort" :rules="{ required: true, message: '请输入排序', trigger: 'blur' }">
|
||||
<el-input-number v-model="formData.sort" :min="0" :max="99999" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</a-form-item>
|
||||
<a-form-item label="案例类型" name="type" :rules="{ required: true, message: '请选择案例类型', trigger: 'change' }">
|
||||
<a-select v-model:value="formData.type" placeholder="请选择" allow-clear show-search :filter-option="filterOption">
|
||||
<a-select-option v-for="item in optionsData.typeList" :key="item.id" :value="item.id">{{ item.name }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="案例状态" name="status" :rules="{ required: true, message: '请选择状态', trigger: 'change' }">
|
||||
<a-radio-group v-model:value="formData.status">
|
||||
<a-radio :value="1">正常</a-radio>
|
||||
<a-radio :value="2">禁用</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" name="sort" :rules="{ required: true, message: '请输入排序', trigger: 'blur' }">
|
||||
<a-input-number v-model:value="formData.sort" :min="0" :max="99999" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogClose">取消</el-button>
|
||||
<el-button :loading="subLoading" type="primary" @click="submit">确定</el-button>
|
||||
<span class="dialog-footer">
|
||||
<a-button @click="dialogClose">取消</a-button>
|
||||
<a-button :loading="subLoading" type="primary" @click="submit">确定</a-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</a-modal>
|
||||
</template>
|
||||
```
|
||||
|
||||
@@ -205,15 +223,17 @@ ui/src/views/tool/example/
|
||||
|
||||
```vue
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from 'ant-design-vue';
|
||||
import { getExampleDetail, exampleAdd, exampleUpdate } from '@/api/tool/example';
|
||||
import { onMounted, reactive, shallowRef, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useLockFn } from '@/utils/useLockFn';
|
||||
|
||||
const emit = defineEmits(['success', 'update:visible']);
|
||||
const formRef = shallowRef<FormInstance>();
|
||||
|
||||
const formData = reactive({
|
||||
id: '', name: '', avatar: '', type: 0, status: 0, sort: 0,
|
||||
id: '', name: '', avatar: '', type: 1, status: 1, sort: 0,
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
@@ -225,11 +245,14 @@ ui/src/views/tool/example/
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value?.validate();
|
||||
props.id ? await exampleUpdate(formData) : await exampleAdd(formData);
|
||||
message('操作成功');
|
||||
message.success('操作成功');
|
||||
emit('update:visible', false);
|
||||
emit('success');
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const dialogClose = () => { emit('update:visible', false); };
|
||||
|
||||
// 防重复提交
|
||||
const { isLock: subLoading, lockFn: submit } = useLockFn(handleSubmit);
|
||||
|
||||
@@ -244,6 +267,11 @@ ui/src/views/tool/example/
|
||||
onMounted(() => {
|
||||
if (props.id) { setFormData(); } else { resetForm(); }
|
||||
});
|
||||
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal && props.id) { setFormData(); }
|
||||
else if (newVal && !props.id) { resetForm(); }
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -257,35 +285,35 @@ ui/src/views/tool/example/
|
||||
|
||||
## 3. 详情弹窗 detail.vue
|
||||
|
||||
详情弹窗用于展示记录的详细信息,使用 ElementPlus 的 `el-descriptions` 组件。
|
||||
详情弹窗用于展示记录的详细信息,使用 Ant Design Vue 的 `a-descriptions` 组件。
|
||||
|
||||
### 核心结构
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-dialog v-model="props.visible" title="案例详情" width="750" :close-on-click-modal="false">
|
||||
<el-descriptions column="2" border>
|
||||
<el-descriptions-item label="案例名称:">{{ formData.name || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="案例图片:">
|
||||
<el-image v-if="formData.avatar" :src="formData.avatar" :preview-src-list="[formData.avatar]" fit="cover" style="width: 80px; height: 80px" />
|
||||
<a-modal v-model:visible="props.visible" title="案例详情" width="750px" @cancel="dialogClose" :mask-closable="false">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="案例名称">{{ formData.name || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="案例图片">
|
||||
<a-image v-if="formData.avatar" :src="formData.avatar" :width="80" :height="80" fit="cover" />
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="案例类型:">{{ getChoiceLabel('type', formData.type) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="案例状态:">{{ getChoiceLabel('status', formData.status) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="排序:">{{ formData.sort || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="案例类型">{{ getChoiceLabel('type', formData.type) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="案例状态">{{ getChoiceLabel('status', formData.status) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="排序">{{ formData.sort || '-' }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
<template #footer>
|
||||
<el-button @click="dialogClose">关闭</el-button>
|
||||
<a-button @click="dialogClose">关闭</a-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</a-modal>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 关键点
|
||||
|
||||
- **el-descriptions**:ElementPlus 的描述列表组件,用于展示详情
|
||||
- **a-descriptions**:Ant Design Vue 的描述列表组件,用于展示详情
|
||||
- **getChoiceLabel**:将枚举值转换为中文标签
|
||||
- **图片预览**:使用 `el-image` 的 `preview-src-list` 实现图片预览
|
||||
- **图片预览**:使用 `a-image` 实现图片预览
|
||||
|
||||
## 4. 表格列定义 columns.ts
|
||||
|
||||
@@ -295,54 +323,89 @@ ui/src/views/tool/example/
|
||||
|
||||
```typescript
|
||||
import { h } from 'vue';
|
||||
import { ElAvatar, ElTag } from 'element-plus';
|
||||
import { Tag, Image } from 'ant-design-vue';
|
||||
|
||||
export const columns = [
|
||||
{ type: 'selection' },
|
||||
{ label: 'ID', prop: 'id', fixed: 'left', width: 50 },
|
||||
{ label: '案例名称', prop: 'name', minWidth: 120 },
|
||||
{
|
||||
label: '案例图片', prop: 'avatar', minWidth: 120,
|
||||
render(record) {
|
||||
return h(ElAvatar, { size: 48, src: record.row.avatar, shape: 'square', fit: 'fill' });
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
fixed: 'left',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: '案例名称',
|
||||
dataIndex: 'name',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '案例图片',
|
||||
dataIndex: 'avatar',
|
||||
width: 150,
|
||||
customRender({ record }) {
|
||||
return record.avatar
|
||||
? h(Image, { src: record.avatar, width: 48, height: 48, fit: 'cover', preview: true })
|
||||
: h('span', '-');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '案例类型', prop: 'type', minWidth: 100,
|
||||
render(record) {
|
||||
title: '案例类型',
|
||||
dataIndex: 'type',
|
||||
width: 100,
|
||||
customRender({ record }) {
|
||||
const statusMap = {
|
||||
1: { text: '类型1', type: 'success' },
|
||||
2: { text: '类型2', type: 'danger' },
|
||||
3: { text: '类型3', type: 'warning' },
|
||||
4: { text: '类型4', type: 'info' },
|
||||
1: { text: '类型1', color: 'success' },
|
||||
2: { text: '类型2', color: 'error' },
|
||||
3: { text: '类型3', color: 'warning' },
|
||||
4: { text: '类型4', color: 'default' },
|
||||
};
|
||||
const status = statusMap[record.row.type];
|
||||
return h(ElTag, { type: status?.type || 'info' }, { default: () => status?.text || record.row.type });
|
||||
const status = statusMap[record.type];
|
||||
return h(Tag, { color: status?.color || 'default' }, { default: () => status?.text || record.type });
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '案例状态', prop: 'status', minWidth: 100,
|
||||
render(record) {
|
||||
title: '案例状态',
|
||||
dataIndex: 'status',
|
||||
width: 100,
|
||||
customRender({ record }) {
|
||||
const statusMap = {
|
||||
1: { text: '正常', type: 'success' },
|
||||
2: { text: '禁用', type: 'danger' },
|
||||
1: { text: '正常', color: 'success' },
|
||||
2: { text: '禁用', color: 'error' },
|
||||
};
|
||||
const status = statusMap[record.row.status];
|
||||
return h(ElTag, { type: status?.type || 'info' }, { default: () => status?.text || record.row.status });
|
||||
const status = statusMap[record.status];
|
||||
return h(Tag, { color: status?.color || 'default' }, { default: () => status?.text || record.status });
|
||||
},
|
||||
},
|
||||
{ label: '排序', prop: 'sort', minWidth: 100 },
|
||||
{ label: '创建人', prop: 'createUser', minWidth: 100 },
|
||||
{ label: '创建时间', prop: 'createTime', width: 180 },
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
dataIndex: 'createUser',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
### 关键点
|
||||
|
||||
- **type: 'selection'**:自动添加复选框列
|
||||
- **render 函数**:使用 Vue 的 `h()` 函数创建虚拟 DOM
|
||||
- **ElTag 组件**:将状态值渲染为彩色标签
|
||||
- **ElAvatar 组件**:将图片 URL 渲染为缩略图
|
||||
- **title / dataIndex**:Ant Design Vue 的列定义字段
|
||||
- **customRender 函数**:使用 Vue 的 `h()` 函数创建虚拟 DOM
|
||||
- **Tag 组件**:将状态值渲染为彩色标签(`color: 'success'` / `'error'`)
|
||||
- **Image 组件**:将图片 URL 渲染为缩略图
|
||||
|
||||
## 5. 搜索表单 querySchemas.ts
|
||||
|
||||
@@ -355,18 +418,19 @@ import { FormSchema } from '@/components/Form/index';
|
||||
|
||||
export const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'name',
|
||||
name: 'name',
|
||||
component: 'Input',
|
||||
label: '案例名称',
|
||||
componentProps: { placeholder: '请输入案例名称', clearable: true },
|
||||
componentProps: { placeholder: '请输入案例名称', clearable: true, allowClear: true },
|
||||
},
|
||||
{
|
||||
field: 'type',
|
||||
name: 'type',
|
||||
component: 'Select',
|
||||
label: '案例类型',
|
||||
componentProps: {
|
||||
placeholder: '请输入案例类型',
|
||||
clearable: true,
|
||||
allowClear: true,
|
||||
options: [
|
||||
{ label: '类型1', value: 1 },
|
||||
{ label: '类型2', value: 2 },
|
||||
@@ -376,12 +440,13 @@ export const schemas: FormSchema[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
name: 'status',
|
||||
component: 'Select',
|
||||
label: '案例状态',
|
||||
componentProps: {
|
||||
placeholder: '请输入案例状态',
|
||||
clearable: true,
|
||||
allowClear: true,
|
||||
options: [
|
||||
{ label: '正常', value: 1 },
|
||||
{ label: '禁用', value: 2 },
|
||||
@@ -393,9 +458,10 @@ export const schemas: FormSchema[] = [
|
||||
|
||||
### 关键点
|
||||
|
||||
- **field**:字段名,与后端查询参数对应
|
||||
- **name**:字段名,与后端查询参数对应
|
||||
- **component**:表单组件类型:`Input` / `Select` / `DatePicker` 等
|
||||
- **componentProps**:组件属性,透传给 ElementPlus 组件
|
||||
- **componentProps**:组件属性,透传给 Ant Design Vue 组件
|
||||
- **allowClear**:Ant Design Vue 的清除属性
|
||||
- **options**:Select 组件的下拉选项
|
||||
|
||||
## 文件关系图
|
||||
@@ -424,7 +490,10 @@ detail.vue(详情弹窗)
|
||||
5. **搜索 Schema 使用 `FormSchema` 类型**:配置化定义表单字段
|
||||
6. **新增/编辑通过 `id` 区分**:0 为新增,非 0 为编辑
|
||||
7. **防重复提交使用 `useLockFn`**:提交时按钮 loading
|
||||
8. **操作列使用 `bodyCell` 插槽**:通过插槽自定义操作按钮
|
||||
9. **确认弹窗使用 `Modal.confirm`**:Ant Design Vue 的确认弹窗
|
||||
10. **消息提示使用 `message.success()`**:Ant Design Vue 的消息提示
|
||||
|
||||
## 总结
|
||||
|
||||
前端页面统一使用 `PageWrapper` + `BasicForm` + `BasicTable` + `TableAction` 四件套,分页由 `BasicTable` 内置处理。每个模块包含 `index.vue`(列表页)、`edit.vue`(编辑弹窗)、`detail.vue`(详情弹窗)、`columns.ts`(列定义)、`querySchemas.ts`(搜索配置)五个文件。
|
||||
前端页面统一使用 `PageWrapper` + `BasicForm` + `BasicTable` + `bodyCell` 插槽四件套,分页由 `BasicTable` 内置处理。每个模块包含 `index.vue`(列表页)、`edit.vue`(编辑弹窗)、`detail.vue`(详情弹窗)、`columns.ts`(列定义)、`querySchemas.ts`(搜索配置)五个文件。
|
||||
|
||||
@@ -16,15 +16,32 @@ from django import forms
|
||||
from application.example import models
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 案例表单验证类
|
||||
# =============================================================
|
||||
|
||||
class ExampleForm(forms.ModelForm):
|
||||
"""
|
||||
案例表单验证类
|
||||
|
||||
用于创建和更新案例信息时的表单验证。
|
||||
验证字段包括:name, avatar, type, status, sort
|
||||
包含案例名称, 案例图片, 案例类型等属性
|
||||
|
||||
验证字段包括:
|
||||
- name: 案例名称
|
||||
- avatar: 案例图片
|
||||
- type: 案例类型
|
||||
- status: 案例状态(1-正常, 2-禁用)
|
||||
- sort: 排序
|
||||
"""
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 案例名称
|
||||
# ----------------------------------------------------------
|
||||
# 字段说明: 案例的名称
|
||||
# 字段类型: 字符串(CharField)
|
||||
# 验证规则: 必填(required=True),最大长度100字符
|
||||
# 业务说明: 案例的核心标识字段,用于列表展示和搜索
|
||||
name = forms.CharField(
|
||||
required=True,
|
||||
max_length=100,
|
||||
@@ -34,7 +51,13 @@ class ExampleForm(forms.ModelForm):
|
||||
}
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 案例图片
|
||||
# ----------------------------------------------------------
|
||||
# 字段说明: 案例的图片
|
||||
# 字段类型: 字符串(CharField)
|
||||
# 验证规则: 可选(required=False),最大长度255字符
|
||||
# 业务说明: 用于案例列表展示和详情页的图片
|
||||
avatar = forms.CharField(
|
||||
required=False,
|
||||
max_length=255,
|
||||
@@ -43,7 +66,13 @@ class ExampleForm(forms.ModelForm):
|
||||
}
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 案例类型
|
||||
# ----------------------------------------------------------
|
||||
# 字段说明: 案例的分类类型
|
||||
# 字段类型: 整数(IntegerField)
|
||||
# 验证规则: 必填(required=True),取值范围1-4
|
||||
# 业务说明: 对案例进行分类管理,支持按类型筛选
|
||||
type = forms.IntegerField(
|
||||
required=True,
|
||||
min_value=1,
|
||||
@@ -55,7 +84,13 @@ class ExampleForm(forms.ModelForm):
|
||||
}
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 案例状态
|
||||
# ----------------------------------------------------------
|
||||
# 字段说明: 案例的显示状态
|
||||
# 字段类型: 整数(IntegerField)
|
||||
# 验证规则: 必填(required=True),取值范围1-2
|
||||
# 业务说明: 1-正常, 2-禁用,控制案例是否在前端展示
|
||||
status = forms.IntegerField(
|
||||
required=True,
|
||||
min_value=1,
|
||||
@@ -67,7 +102,14 @@ class ExampleForm(forms.ModelForm):
|
||||
}
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 排序
|
||||
# ----------------------------------------------------------
|
||||
# 字段说明: 案例的排序权重
|
||||
# 字段类型: 整数(IntegerField)
|
||||
# 验证规则: 必填(required=True)
|
||||
# 排序规则: 数值越小越靠前
|
||||
# 业务说明: 手动控制案例在列表中的显示顺序
|
||||
sort = forms.IntegerField(
|
||||
required=True,
|
||||
error_messages={
|
||||
@@ -75,8 +117,23 @@ class ExampleForm(forms.ModelForm):
|
||||
}
|
||||
)
|
||||
|
||||
# =============================================================
|
||||
# 表单元数据配置
|
||||
# =============================================================
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
表单元数据配置类
|
||||
|
||||
定义表单绑定的模型和需要验证的字段。
|
||||
"""
|
||||
|
||||
# 绑定模型
|
||||
# 指定该表单对应的数据模型为 Example(案例)
|
||||
model = models.Example
|
||||
|
||||
# 指定需要验证的字段
|
||||
# 只验证以下字段,其他字段不进行表单验证
|
||||
fields = [
|
||||
'name',
|
||||
'avatar',
|
||||
|
||||
@@ -17,6 +17,10 @@ from application.models import BaseModel
|
||||
from utils.common import get_table_name
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 案例模型类
|
||||
# =============================================================
|
||||
|
||||
class Example(BaseModel):
|
||||
"""
|
||||
案例模型类
|
||||
@@ -29,29 +33,53 @@ class Example(BaseModel):
|
||||
- create_time: 创建时间
|
||||
- update_user: 更新人
|
||||
- update_time: 更新时间
|
||||
|
||||
主要业务字段:
|
||||
- name: 案例名称
|
||||
- avatar: 案例图片
|
||||
- type: 案例类型
|
||||
- status: 案例状态(1-正常, 2-禁用)
|
||||
- sort: 排序权重(数值越小越靠前)
|
||||
"""
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 案例名称
|
||||
# ----------------------------------------------------------
|
||||
# 字段说明: 案例的名称
|
||||
# 字段类型: 字符串(CharField)
|
||||
# 约束: 非空(null=False),最大长度100字符
|
||||
# 索引: 添加了数据库索引(db_index=True)以提升查询效率
|
||||
# 作用: 案例的核心标识字段,用于列表展示和搜索
|
||||
name = models.CharField(
|
||||
null=False,
|
||||
max_length=100,
|
||||
db_index=True,
|
||||
verbose_name="案例名称",
|
||||
help_text="案例名称",
|
||||
db_comment='案例名称'
|
||||
db_comment='案例名称' # 数据库字段注释
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 案例图片
|
||||
# ----------------------------------------------------------
|
||||
# 字段说明: 案例的图片
|
||||
# 字段类型: 字符串(CharField)
|
||||
# 约束: 允许为空(null=True, blank=True),最大长度255字符
|
||||
# 存储: 存储的是相对路径,通过 get_file_url() 生成完整访问URL
|
||||
# 作用: 用于案例列表展示和详情页的图片
|
||||
avatar = models.CharField(
|
||||
null=True,
|
||||
blank=True,
|
||||
max_length=255,
|
||||
verbose_name="案例图片",
|
||||
help_text="案例图片",
|
||||
db_comment='案例图片'
|
||||
db_comment='案例图片' # 数据库字段注释
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 案例类型
|
||||
# ----------------------------------------------------------
|
||||
# 类型常量定义
|
||||
TYPE_CHOICES = (
|
||||
(1, "类型1"),
|
||||
(2, "类型2"),
|
||||
@@ -59,44 +87,98 @@ class Example(BaseModel):
|
||||
(4, "类型4"),
|
||||
)
|
||||
|
||||
# 字段说明: 案例的分类类型
|
||||
# 字段类型: 整数(IntegerField)
|
||||
# 约束: 非空(null=False)
|
||||
# 选择项: 使用 TYPE_CHOICES 限制可选值
|
||||
# 作用: 对案例进行分类管理,支持按类型筛选
|
||||
type = models.IntegerField(
|
||||
null=False,
|
||||
choices=TYPE_CHOICES,
|
||||
verbose_name="案例类型:1-类型1 2-类型2 3-类型3 4-类型4",
|
||||
help_text="案例类型:1-类型1 2-类型2 3-类型3 4-类型4",
|
||||
db_comment='案例类型:1-类型1 2-类型2 3-类型3 4-类型4'
|
||||
db_comment='案例类型:1-类型1 2-类型2 3-类型3 4-类型4' # 数据库字段注释
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 案例状态
|
||||
# ----------------------------------------------------------
|
||||
# 状态常量定义
|
||||
STATUS_CHOICES = (
|
||||
(1, "正常"),
|
||||
(2, "禁用"),
|
||||
(1, "正常"), # 正常状态:案例正常显示
|
||||
(2, "禁用"), # 禁用状态:案例不对外展示
|
||||
)
|
||||
|
||||
# 字段说明: 案例的显示状态
|
||||
# 字段类型: 整数(IntegerField)
|
||||
# 约束: 非空(null=False)
|
||||
# 选择项: 使用 STATUS_CHOICES 限制可选值
|
||||
# 作用: 控制案例是否在前端展示
|
||||
status = models.IntegerField(
|
||||
null=False,
|
||||
choices=STATUS_CHOICES,
|
||||
verbose_name="案例状态:1-正常 2-禁用",
|
||||
help_text="案例状态:1-正常 2-禁用",
|
||||
db_comment='案例状态:1-正常 2-禁用'
|
||||
db_comment='案例状态:1-正常 2-禁用' # 数据库字段注释
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 排序
|
||||
# ----------------------------------------------------------
|
||||
# 字段说明: 案例的排序权重
|
||||
# 字段类型: 整数(IntegerField)
|
||||
# 约束: 非空(null=False)
|
||||
# 排序规则: 数值越小越靠前(升序排列)
|
||||
# 作用: 手动控制案例在列表中的显示顺序
|
||||
sort = models.IntegerField(
|
||||
null=False,
|
||||
verbose_name="排序",
|
||||
help_text="排序",
|
||||
db_comment='排序'
|
||||
db_comment='排序' # 数据库字段注释
|
||||
)
|
||||
|
||||
# =============================================================
|
||||
# 模型元数据配置
|
||||
# =============================================================
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
模型元数据配置类
|
||||
|
||||
定义模型在数据库中的表名、显示名称、默认排序规则等。
|
||||
"""
|
||||
|
||||
# 数据表名
|
||||
# 使用配置文件中的表前缀 + "example",避免与其他应用表名冲突
|
||||
db_table = get_table_name('example')
|
||||
|
||||
# 表注释(Django 3.2+ 支持)
|
||||
# 在数据库中生成 COMMENT 语句,便于数据库管理
|
||||
db_table_comment = "案例表"
|
||||
|
||||
# 模型在admin后台显示的友好名称(单数)
|
||||
verbose_name = "案例表"
|
||||
|
||||
# 模型在admin后台显示的友好名称(复数)
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
# 默认排序规则
|
||||
# 按照 sort 字段升序排列(数值小的在前),确保显示顺序可控
|
||||
ordering = ("sort",)
|
||||
|
||||
# =============================================================
|
||||
# 实例方法
|
||||
# =============================================================
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
模型的字符串表示方法
|
||||
|
||||
当需要将模型实例转换为字符串时调用,如admin后台显示、日志输出等。
|
||||
|
||||
Returns:
|
||||
str: 包含案例名称的字符串
|
||||
"""
|
||||
return f"案例{self.name}"
|
||||
```
|
||||
|
||||
|
||||
@@ -101,10 +101,10 @@ class PermissionRequired(PermissionRequiredMixin):
|
||||
|
||||
```vue
|
||||
<!-- 新增按钮 -->
|
||||
<el-button v-perm="['sys:example:add']">添加案例</el-button>
|
||||
<a-button v-perm="['sys:example:add']">添加案例</a-button>
|
||||
|
||||
<!-- 批量删除按钮 -->
|
||||
<el-button v-perm="['sys:example:batchDelete']">批量删除</el-button>
|
||||
<a-button v-perm="['sys:example:batchDelete']">批量删除</a-button>
|
||||
```
|
||||
|
||||
操作栏按钮使用 `auth` 属性:
|
||||
|
||||
@@ -17,30 +17,90 @@ application/urls.py # 主路由
|
||||
from django.urls import path
|
||||
from application.example import views
|
||||
|
||||
"""
|
||||
案例模块路由配置
|
||||
=============================================================
|
||||
|
||||
URL端点概览:
|
||||
------------------------------------------------------------------------
|
||||
| 功能说明 | 请求方法 | URL路径 | 视图类 |
|
||||
|-----------------|----------|-----------------------------|-------------------------|
|
||||
| 分页查询 | GET | /example/page | ExamplePageView |
|
||||
| 列表查询 | GET | /example/list | ExampleListView |
|
||||
| 详情查询 | GET | /example/detail/<id> | ExampleDetailView |
|
||||
| 添加案例 | POST | /example/add | ExampleAddView |
|
||||
| 更新案例 | PUT | /example/update | ExampleUpdateView |
|
||||
| 删除案例 | DELETE | /example/delete/<ids>| ExampleDeleteView |
|
||||
| 设置状态 | PUT | /example/status | ExampleStatusView |
|
||||
| 获取列表 | GET | /example/data | ExampleDataView |
|
||||
------------------------------------------------------------------------
|
||||
|
||||
URL参数说明:
|
||||
- id: int类型,案例ID(用于查询详情)
|
||||
- id: str类型,逗号分隔的案例ID字符串(用于批量删除)
|
||||
|
||||
使用示例:
|
||||
1. 分页查询:GET /api/example/page?pageNo=1&pageSize=10
|
||||
2. 列表查询:GET /api/example/list
|
||||
3. 详情查询:GET /api/example/detail/5
|
||||
4. 添加案例:POST /api/example/add
|
||||
5. 更新案例:PUT /api/example/update
|
||||
6. 删除案例:DELETE /api/example/delete/1,2,3
|
||||
7. 设置状态:PUT /api/example/status
|
||||
8. 获取列表:GET /api/example/data
|
||||
"""
|
||||
|
||||
# 案例模块路由配置
|
||||
urlpatterns = [
|
||||
# 查询案例分页列表
|
||||
# URL: /example/page
|
||||
# 请求方法: GET
|
||||
# 功能: 获取案例分页数据,支持按案例名称、案例类型、案例状态等条件筛选
|
||||
path('page', views.ExamplePageView.as_view()),
|
||||
|
||||
# 查询案例数据列表(支持筛选)
|
||||
# URL: /example/list
|
||||
# 请求方法: GET
|
||||
# 功能: 获取所有案例的列表数据,支持按条件筛选,用于下拉选择等前端组件
|
||||
path('list', views.ExampleListView.as_view()),
|
||||
|
||||
# 查询案例详情
|
||||
# URL: /example/detail/<int:id>
|
||||
# 请求方法: GET
|
||||
# URL参数: id - 案例ID(整数类型)
|
||||
# 功能: 根据ID获取指定案例的详细信息
|
||||
path('detail/<int:id>', views.ExampleDetailView.as_view()),
|
||||
|
||||
# 添加案例
|
||||
# URL: /example/add
|
||||
# 请求方法: POST
|
||||
# 功能: 创建新的案例记录,请求体需包含案例信息(name、avatar、type、status、sort等)
|
||||
path('add', views.ExampleAddView.as_view()),
|
||||
|
||||
# 更新案例
|
||||
# URL: /example/update
|
||||
# 请求方法: PUT
|
||||
# 功能: 更新现有案例信息,请求体需包含案例ID和更新内容
|
||||
path('update', views.ExampleUpdateView.as_view()),
|
||||
|
||||
# 删除案例
|
||||
# URL: /example/delete/<str:id>
|
||||
# 请求方法: DELETE
|
||||
# URL参数: id - 案例ID字符串,支持单个ID或逗号分隔的多个ID(如 "1,2,3")
|
||||
# 功能: 删除指定案例(逻辑删除,设置is_delete=True)
|
||||
path('delete/<str:id>', views.ExampleDeleteView.as_view()),
|
||||
|
||||
# 设置案例状态
|
||||
# URL: /example/status
|
||||
# 请求方法: PUT
|
||||
# 功能: 更新案例的启用/禁用状态
|
||||
path('status', views.ExampleStatusView.as_view()),
|
||||
|
||||
# 查询案例数据列表
|
||||
path('list', views.ExampleDataView.as_view()),
|
||||
# URL: /example/data
|
||||
# 请求方法: GET
|
||||
# 功能: 获取所有启用案例的列表数据,用于下拉选择等前端组件
|
||||
path('data', views.ExampleDataView.as_view()),
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
@@ -23,93 +23,305 @@ from application.operation_log.constants import LogType
|
||||
from application.operation_log.decorators import operation_log
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 分页查询视图
|
||||
# =============================================================
|
||||
|
||||
@method_decorator(check_login, name="get")
|
||||
class ExamplePageView(PermissionRequired, View):
|
||||
"""案例分页数据查询视图"""
|
||||
"""
|
||||
案例分页数据查询视图
|
||||
|
||||
处理GET请求,返回案例列表的分页数据。
|
||||
支持按案例名称、案例类型、案例状态等条件筛选。
|
||||
|
||||
URL: /example/page
|
||||
请求方法: GET
|
||||
权限要求:sys:example:page
|
||||
"""
|
||||
|
||||
# 方法权限标识
|
||||
permission_required = ("sys:example:page",)
|
||||
|
||||
@operation_log(title="案例管理-查询分页列表", log_type=LogType.QUERY)
|
||||
@operation_log(
|
||||
title="案例管理-查询分页列表",
|
||||
log_type=LogType.QUERY
|
||||
)
|
||||
def get(self, request):
|
||||
"""
|
||||
处理GET请求,返回案例分页数据
|
||||
|
||||
Args:
|
||||
request: HttpRequest对象,包含分页参数和筛选条件
|
||||
- pageNo: 当前页码
|
||||
- pageSize: 每页记录数
|
||||
- name: 案例名称(可选,模糊查询)
|
||||
- type: 案例类型(可选,精确匹配)
|
||||
- status: 案例状态(可选,精确匹配)
|
||||
|
||||
Returns:
|
||||
JsonResponse: 包含分页数据的JSON响应
|
||||
"""
|
||||
result = services.get_example_page(request)
|
||||
return result
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 列表查询视图(不分页)
|
||||
# =============================================================
|
||||
|
||||
@method_decorator(check_login, name='get')
|
||||
class ExampleListView(View):
|
||||
"""案例列表获取视图(支持筛选,不分页)"""
|
||||
"""
|
||||
案例列表获取视图(支持筛选,不分页)
|
||||
|
||||
处理GET请求,返回所有案例的列表数据。
|
||||
支持按案例名称、案例类型、案例状态等条件筛选。
|
||||
|
||||
URL: /example/list
|
||||
请求方法: GET
|
||||
权限要求:需要登录
|
||||
"""
|
||||
|
||||
def get(self, request):
|
||||
"""
|
||||
处理GET请求,返回案例列表
|
||||
|
||||
Args:
|
||||
request: HttpRequest对象
|
||||
|
||||
Returns:
|
||||
JsonResponse: 包含案例列表的JSON响应
|
||||
"""
|
||||
result = services.get_example_list(request)
|
||||
return R.ok(data=result)
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 详情查询视图
|
||||
# =============================================================
|
||||
|
||||
@method_decorator(check_login, name="get")
|
||||
class ExampleDetailView(PermissionRequired, View):
|
||||
"""案例详情查询视图"""
|
||||
"""
|
||||
案例详情查询视图
|
||||
|
||||
处理GET请求,根据案例ID返回案例详细信息。
|
||||
|
||||
URL: /example/detail/<int:id>
|
||||
请求方法: GET
|
||||
权限要求:sys:example:detail
|
||||
"""
|
||||
|
||||
# 方法权限标识
|
||||
permission_required = ("sys:example:detail",)
|
||||
|
||||
@operation_log(title="案例管理-查询记录详情", log_type=LogType.QUERY)
|
||||
@operation_log(
|
||||
title="案例管理-查询记录详情",
|
||||
log_type=LogType.QUERY
|
||||
)
|
||||
def get(self, request, id):
|
||||
"""
|
||||
处理GET请求,返回案例详情
|
||||
|
||||
Args:
|
||||
request: HttpRequest对象
|
||||
id: 案例ID(URL参数)
|
||||
|
||||
Returns:
|
||||
JsonResponse: 包含案例详情的JSON响应
|
||||
"""
|
||||
data = services.get_example_detail(id)
|
||||
return R.ok(data=data)
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 添加视图
|
||||
# =============================================================
|
||||
|
||||
@method_decorator(check_login, name="post")
|
||||
class ExampleAddView(PermissionRequired, View):
|
||||
"""添加案例视图"""
|
||||
"""
|
||||
添加案例视图
|
||||
|
||||
处理POST请求,创建新的案例记录。
|
||||
请求体需包含案例信息(name、avatar、type、status、sort等)。
|
||||
|
||||
URL: /example/add
|
||||
请求方法: POST
|
||||
权限要求:sys:example:add
|
||||
"""
|
||||
|
||||
# 方法权限标识
|
||||
permission_required = ("sys:example:add",)
|
||||
|
||||
@operation_log(title="案例管理-添加记录", log_type=LogType.ADD)
|
||||
@operation_log(
|
||||
title="案例管理-添加记录",
|
||||
log_type=LogType.ADD
|
||||
)
|
||||
def post(self, request):
|
||||
"""
|
||||
处理POST请求,添加案例
|
||||
|
||||
Args:
|
||||
request: HttpRequest对象,请求体包含案例信息
|
||||
|
||||
Returns:
|
||||
JsonResponse: 操作结果的JSON响应
|
||||
"""
|
||||
# 演示模式检查
|
||||
if DJANGO_DEMO:
|
||||
return R.failed("演示环境,暂无操作权限")
|
||||
result = services.add_example(request)
|
||||
return result
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 更新视图
|
||||
# =============================================================
|
||||
|
||||
@method_decorator(check_login, name="put")
|
||||
class ExampleUpdateView(PermissionRequired, View):
|
||||
"""更新案例视图"""
|
||||
"""
|
||||
更新案例视图
|
||||
|
||||
处理PUT请求,更新现有案例信息。
|
||||
请求体需包含案例ID和更新内容。
|
||||
|
||||
URL: /example/update
|
||||
请求方法: PUT
|
||||
权限要求:sys:example:update
|
||||
"""
|
||||
|
||||
# 方法权限标识
|
||||
permission_required = ('sys:example:update',)
|
||||
|
||||
@operation_log(title="案例管理-更新记录", log_type=LogType.UPDATE)
|
||||
@operation_log(
|
||||
title="案例管理-更新记录",
|
||||
log_type=LogType.UPDATE
|
||||
)
|
||||
def put(self, request):
|
||||
"""
|
||||
处理PUT请求,更新案例
|
||||
|
||||
Args:
|
||||
request: HttpRequest对象,请求体包含更新信息
|
||||
|
||||
Returns:
|
||||
JsonResponse: 操作结果的JSON响应
|
||||
"""
|
||||
# 演示模式检查
|
||||
if DJANGO_DEMO:
|
||||
return R.failed("演示环境,暂无操作权限")
|
||||
result = services.update_example(request)
|
||||
return result
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 删除视图
|
||||
# =============================================================
|
||||
|
||||
@method_decorator(check_login, name="delete")
|
||||
class ExampleDeleteView(PermissionRequired, View):
|
||||
"""删除案例视图"""
|
||||
"""
|
||||
删除案例视图
|
||||
|
||||
处理DELETE请求,删除指定案例(逻辑删除)。
|
||||
支持批量删除,多个ID用逗号分隔。
|
||||
|
||||
URL: /example/delete/<str:id>
|
||||
请求方法: DELETE
|
||||
权限要求:sys:example:delete
|
||||
"""
|
||||
|
||||
# 方法权限标识
|
||||
permission_required = ('sys:example:delete',)
|
||||
|
||||
@operation_log(title="案例管理-删除记录", log_type=LogType.DELETE)
|
||||
@operation_log(
|
||||
title="案例管理-删除记录",
|
||||
log_type=LogType.DELETE
|
||||
)
|
||||
def delete(self, request, id):
|
||||
"""
|
||||
处理DELETE请求,删除案例
|
||||
|
||||
Args:
|
||||
request: HttpRequest对象
|
||||
id: 案例ID字符串,支持逗号分隔的多个ID(如 "1,2,3")
|
||||
|
||||
Returns:
|
||||
JsonResponse: 操作结果的JSON响应
|
||||
"""
|
||||
# 演示模式检查
|
||||
if DJANGO_DEMO:
|
||||
return R.failed("演示环境,暂无操作权限")
|
||||
result = services.delete_examples(id)
|
||||
return result
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 状态更新视图
|
||||
# =============================================================
|
||||
|
||||
@method_decorator(check_login, name="put")
|
||||
class ExampleStatusView(PermissionRequired, View):
|
||||
"""设置案例状态视图"""
|
||||
"""
|
||||
设置案例状态视图
|
||||
|
||||
处理PUT请求,更新案例的启用/禁用状态。
|
||||
|
||||
URL: /example/status
|
||||
请求方法: PUT
|
||||
权限要求:sys:example:status
|
||||
"""
|
||||
|
||||
# 方法权限标识
|
||||
permission_required = ('sys:example:status',)
|
||||
|
||||
def put(self, request):
|
||||
"""
|
||||
处理PUT请求,更新案例状态
|
||||
|
||||
Args:
|
||||
request: HttpRequest对象,请求体包含案例ID和新状态
|
||||
|
||||
Returns:
|
||||
JsonResponse: 操作结果的JSON响应
|
||||
"""
|
||||
# 演示模式检查
|
||||
if DJANGO_DEMO:
|
||||
return R.failed("演示环境,暂无操作权限")
|
||||
result = services.update_example_status(request)
|
||||
return result
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 数据列表视图(下拉选择用)
|
||||
# =============================================================
|
||||
|
||||
@method_decorator(check_login, name='get')
|
||||
class ExampleDataView(View):
|
||||
"""案例列表获取视图"""
|
||||
"""
|
||||
案例列表获取视图
|
||||
|
||||
处理GET请求,返回所有启用案例的列表数据。
|
||||
用于下拉选择等前端组件。
|
||||
|
||||
URL: /example/data
|
||||
请求方法: GET
|
||||
权限要求:需要登录
|
||||
"""
|
||||
|
||||
def get(self, request):
|
||||
"""
|
||||
处理GET请求,返回案例数据列表
|
||||
|
||||
Args:
|
||||
request: HttpRequest对象
|
||||
|
||||
Returns:
|
||||
JsonResponse: 包含案例列表的JSON响应
|
||||
"""
|
||||
result = services.get_example_data()
|
||||
return R.ok(data=result)
|
||||
```
|
||||
|
||||
@@ -53,65 +53,100 @@ JsonResponse <-- Django HTTP响应
|
||||
|
||||
```python
|
||||
class BaseModel(models.Model):
|
||||
"""基础模型抽象类"""
|
||||
"""
|
||||
基础模型抽象类
|
||||
==============
|
||||
功能:提供所有模型的公共字段,如主键、创建人、创建时间等
|
||||
特点:定义为抽象类,不会在数据库中创建对应的表
|
||||
用途:被其他业务模型继承,实现字段复用
|
||||
|
||||
继承此基类的模型会自动拥有以下6个公共字段:
|
||||
- id: 自增主键
|
||||
- create_user: 创建人
|
||||
- create_time: 创建时间
|
||||
- update_user: 更新人
|
||||
- update_time: 更新时间
|
||||
- is_delete: 逻辑删除标识
|
||||
|
||||
注意:公共字段会自动排列在业务字段之后,使数据库表结构更清晰
|
||||
"""
|
||||
|
||||
# ==================== 公共字段定义 ====================
|
||||
|
||||
# 主键ID字段
|
||||
# 自增主键,每个模型的唯一标识符
|
||||
id = models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='主键ID',
|
||||
db_comment='主键ID'
|
||||
auto_created=True, # 自动创建主键字段
|
||||
primary_key=True, # 设置为主键
|
||||
serialize=False, # 不参与序列化(避免在API响应中暴露)
|
||||
verbose_name='主键ID', # 字段中文名称
|
||||
db_comment='主键ID' # 数据库字段注释
|
||||
)
|
||||
|
||||
# 创建人字段
|
||||
# 记录数据由谁创建,通常关联用户表的用户名或ID
|
||||
# 例如:admin, zhangsan, system
|
||||
create_user = models.CharField(
|
||||
null=True,
|
||||
max_length=50,
|
||||
default=None,
|
||||
db_default=None,
|
||||
verbose_name='创建人',
|
||||
db_comment='创建人'
|
||||
null=True, # 数据库允许为NULL
|
||||
max_length=50, # 最大长度50个字符
|
||||
default=None, # 默认值为None
|
||||
db_default=None, # 数据库层默认值
|
||||
verbose_name='创建人', # 字段中文名称
|
||||
db_comment='创建人' # 数据库字段注释
|
||||
)
|
||||
|
||||
# 创建时间字段
|
||||
# 自动记录数据创建的时间戳
|
||||
# auto_now_add: 仅在创建时自动设置当前时间,后续更新不会改变
|
||||
create_time = models.DateTimeField(
|
||||
null=True,
|
||||
auto_now_add=True,
|
||||
verbose_name="创建时间",
|
||||
max_length=11,
|
||||
db_comment='创建时间'
|
||||
null=True, # 数据库允许为NULL
|
||||
auto_now_add=True, # 创建时自动设为当前时间
|
||||
verbose_name="创建时间", # 字段中文名称
|
||||
max_length=11, # 时间戳长度
|
||||
db_comment='创建时间' # 数据库字段注释
|
||||
)
|
||||
|
||||
# 更新人字段
|
||||
# 记录数据最后由谁更新,通常关联用户表的用户名或ID
|
||||
# null=True: 允许数据库为NULL(新创建记录时可能没有更新人)
|
||||
update_user = models.CharField(
|
||||
null=True,
|
||||
max_length=50,
|
||||
default=None,
|
||||
db_default=None,
|
||||
verbose_name='更新人',
|
||||
db_comment='更新人'
|
||||
null=True, # 数据库允许为NULL
|
||||
max_length=50, # 最大长度50个字符
|
||||
default=None, # 默认值为None
|
||||
db_default=None, # 数据库层默认值
|
||||
verbose_name='更新人', # 字段中文名称
|
||||
db_comment='更新人' # 数据库字段注释
|
||||
)
|
||||
|
||||
# 更新时间字段
|
||||
# 自动记录数据最后更新的时间戳
|
||||
# auto_now: 每次保存(更新)时自动设为当前时间
|
||||
update_time = models.DateTimeField(
|
||||
null=True,
|
||||
auto_now=True,
|
||||
verbose_name="更新时间",
|
||||
max_length=11,
|
||||
db_comment='更新时间'
|
||||
null=True, # 数据库允许为NULL
|
||||
auto_now=True, # 每次保存时自动更新为当前时间
|
||||
verbose_name="更新时间", # 字段中文名称
|
||||
max_length=11, # 时间戳长度
|
||||
db_comment='更新时间' # 数据库字段注释
|
||||
)
|
||||
|
||||
# 逻辑删除标识
|
||||
# 用于软删除,避免物理删除数据造成的数据丢失
|
||||
# 0:正常(默认值)| 1:已删除
|
||||
is_delete = models.BooleanField(
|
||||
default=0,
|
||||
db_default=0,
|
||||
verbose_name="逻辑删除",
|
||||
db_comment='逻辑删除:0-正常 1-已删除'
|
||||
default=0, # 默认为0,表示正常
|
||||
db_default=0, # 数据库层默认值
|
||||
verbose_name="逻辑删除", # 字段中文名称
|
||||
db_comment='逻辑删除:0-正常 1-已删除' # 数据库字段注释
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True # 抽象模型,不创建数据库表
|
||||
"""
|
||||
元数据配置
|
||||
"""
|
||||
# 定义为抽象模型类
|
||||
# abstract = True 表示这个模型是抽象的,不会在数据库中创建对应的表
|
||||
# 子类模型会继承这些字段,并在各自对应的表中创建这些列
|
||||
abstract = True
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
|
||||
@@ -65,7 +65,7 @@ REDIS_DB = int(os.getenv('REDIS_DB', 0))
|
||||
|------|--------|------|
|
||||
| `REDIS_HOST` | `127.0.0.1` | Redis服务器地址 |
|
||||
| `REDIS_PORT` | `6379` | Redis端口 |
|
||||
| `REDIS_PASSWORD` | `123456` | Redis密码 |
|
||||
| `REDIS_PASSWORD` | `123456` | Redis密码(`.env` 中配置,无密码时留空) |
|
||||
| `REDIS_DB` | `0` | Redis数据库索引(0-15) |
|
||||
|
||||
## 4.3 缓存应用场景
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
## 4.1 项目根目录
|
||||
|
||||
```
|
||||
DjangoAdmin_Django_EleVue/
|
||||
DjangoAdmin_Django_AntdVue/
|
||||
|
|
||||
+-- application/ # Django后端应用主目录
|
||||
+-- middleware/ # 自定义中间件
|
||||
|
||||
@@ -162,7 +162,7 @@ public/templates/
|
||||
|
||||
### 4.4.2 模板语法示例
|
||||
|
||||
```jinja2
|
||||
```jinja
|
||||
{# models.py.tpl #}
|
||||
from django.db import models
|
||||
from application.models import BaseModel
|
||||
@@ -294,7 +294,6 @@ python manage.py runserver # 启动开发服务器
|
||||
python manage.py makemigrations <app> # 生成迁移文件
|
||||
python manage.py migrate # 执行迁移
|
||||
python manage.py createsuperuser # 创建超级用户
|
||||
python manage.py add_full_comments # 回填数据库字段注释
|
||||
```
|
||||
|
||||
## 4.8 前端扩展
|
||||
@@ -310,7 +309,7 @@ VALUES ('案例管理', '/example', 'tool/example/index', 'sys:example:page', 0,
|
||||
|
||||
### 4.8.2 组件扩展
|
||||
|
||||
前端使用 Vue3 + ElementPlus,可通过以下方式扩展:
|
||||
前端使用 Vue3 + Ant Design Vue,可通过以下方式扩展:
|
||||
|
||||
1. **自定义组件**:在 `ui/src/components/` 目录下创建新组件
|
||||
2. **组合式函数**:在 `ui/src/hooks/` 目录下创建自定义 hooks
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
## 4.1 整体架构
|
||||
|
||||
项目采用前后端分离的 B/S 架构,后端基于 Django 6.0.3 提供 RESTful API,前端基于 Vue3 + ElementPlus 构建单页应用。系统通过 Nginx 反向代理统一入口,实现静态资源托管与 API 转发。
|
||||
项目采用前后端分离的 B/S 架构,后端基于 Django 6.0.3 提供 RESTful API,前端基于 Vue3 + Ant Design Vue 构建单页应用。系统通过 Nginx 反向代理统一入口,实现静态资源托管与 API 转发。
|
||||
|
||||
### 架构图
|
||||
|
||||
```
|
||||
浏览器 (Vue3 + ElementPlus)
|
||||
浏览器 (Vue3 + Ant Design Vue)
|
||||
|
|
||||
v
|
||||
Nginx (反向代理)
|
||||
@@ -117,14 +117,14 @@ application/<module>/
|
||||
| 缓存 | Redis | 6.0+ |
|
||||
| 认证 | JWT (PyJWT) | - |
|
||||
| 前端框架 | Vue3 | 3.x |
|
||||
| UI组件库 | ElementPlus | - |
|
||||
| UI组件库 | Ant Design Vue | - |
|
||||
| 构建工具 | Vite | - |
|
||||
| 状态管理 | Pinia | - |
|
||||
|
||||
## 4.7 目录结构
|
||||
|
||||
```
|
||||
DjangoAdmin_Django_EleVue/
|
||||
DjangoAdmin_Django_AntdVue/
|
||||
+-- application/ # Django后端应用
|
||||
| +-- settings.py # 项目配置
|
||||
| +-- urls.py # 主路由
|
||||
|
||||
@@ -192,8 +192,8 @@ const routes = [
|
||||
### 组件按需导入
|
||||
|
||||
```typescript
|
||||
// ElementPlus 按需导入
|
||||
import { ElButton, ElTable, ElForm } from 'element-plus';
|
||||
// Ant Design Vue 按需导入
|
||||
import { Button, Table, Form } from 'ant-design-vue';
|
||||
```
|
||||
|
||||
### 图片懒加载
|
||||
@@ -212,7 +212,7 @@ import(/* webpackChunkName: "editor" */ '@wangeditor/editor');
|
||||
|
||||
## Gunicorn Worker 配置
|
||||
|
||||
```ini
|
||||
```bash
|
||||
# gunicorn.conf.py
|
||||
import multiprocessing
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ def parse_payload(token):
|
||||
- TypeScript 提供类型安全,减少运行时错误
|
||||
- 生态成熟,社区活跃
|
||||
|
||||
### ElementPlus
|
||||
### Ant Design Vue
|
||||
|
||||
**选型理由**:
|
||||
- 基于 Vue3 的企业级 UI 组件库
|
||||
@@ -197,7 +197,7 @@ jinja2
|
||||
{
|
||||
"dependencies": {
|
||||
"vue": "^3.x",
|
||||
"element-plus": "^2.x",
|
||||
"ant-design-vue": "^3.x",
|
||||
"pinia": "^2.x",
|
||||
"vue-router": "^4.x",
|
||||
"axios": "^1.x"
|
||||
|
||||
@@ -145,16 +145,16 @@ def update_category(request):
|
||||
|
||||
### 树形选择组件
|
||||
|
||||
分类选择通常使用 el-tree-select 组件:
|
||||
分类选择通常使用 a-tree-select 组件:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-tree-select
|
||||
v-model="form.category_id"
|
||||
:data="categoryTree"
|
||||
:props="{ label: 'categoryName', value: 'id' }"
|
||||
<a-tree-select
|
||||
v-model:value="form.category_id"
|
||||
:tree-data="categoryTree"
|
||||
:field-names="{ children: 'children', label: 'categoryName', value: 'id' }"
|
||||
placeholder="请选择分类"
|
||||
check-strictly
|
||||
:allow-clear="true"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
@@ -166,9 +166,7 @@ def update_category(request):
|
||||
```vue
|
||||
<template>
|
||||
<BasicTable :data="categoryTree" row-key="id" default-expand-all>
|
||||
<el-table-column prop="categoryName" label="分类名称" />
|
||||
<el-table-column prop="categoryCode" label="分类编码" />
|
||||
<el-table-column prop="sort" label="排序" />
|
||||
<!-- 列通过 columns.ts 配置 -->
|
||||
</BasicTable>
|
||||
</template>
|
||||
```
|
||||
@@ -178,11 +176,10 @@ def update_category(request):
|
||||
支持拖拽调整分类层级和顺序:
|
||||
|
||||
```vue
|
||||
<el-tree
|
||||
:data="categoryTree"
|
||||
<a-tree
|
||||
:tree-data="categoryTree"
|
||||
draggable
|
||||
allow-drop="allowDrop"
|
||||
@node-drop="handleDrop"
|
||||
@drop="handleDrop"
|
||||
/>
|
||||
```
|
||||
|
||||
@@ -197,5 +194,5 @@ def update_category(request):
|
||||
4. 排序控制:sort 字段控制分类显示顺序
|
||||
5. 删除校验:删除前检查子级,保证数据完整性
|
||||
6. 循环引用校验:更新时防止 A→B→C→A 死循环
|
||||
7. 前端集成:el-tree-select、树形表格、拖拽排序
|
||||
7. 前端集成:a-tree-select、树形表格、拖拽排序
|
||||
```
|
||||
|
||||
@@ -190,12 +190,13 @@ def delete_cities(city_ids):
|
||||
|
||||
## 前端级联选择
|
||||
|
||||
前端使用 `el-cascader` 组件实现省市区三级联动选择:
|
||||
前端使用 `a-cascader` 组件实现省市区三级联动选择:
|
||||
|
||||
```vue
|
||||
<el-cascader
|
||||
v-model="selectedRegion"
|
||||
:props="cascaderProps"
|
||||
<a-cascader
|
||||
v-model:value="selectedRegion"
|
||||
:options="options"
|
||||
:load-data="loadData"
|
||||
placeholder="请选择省/市/区"
|
||||
/>
|
||||
|
||||
@@ -237,7 +238,7 @@ python manage.py migrate
|
||||
1. 五级行政区划:省/市/区/街道/社区,通过 parent_code 形成树形结构
|
||||
2. 丰富字段:含经纬度、拼音、邮编等信息
|
||||
3. 用户关联:用户表通过四级编码关联城市表
|
||||
4. 级联选择:前端通过 el-cascader 组件实现级联选择
|
||||
4. 级联选择:前端通过 a-cascader 组件实现级联选择
|
||||
5. 删除校验:删除前检查子级,保证数据完整性
|
||||
6. 批量导入:内置全国行政区划数据
|
||||
```
|
||||
|
||||
@@ -209,7 +209,7 @@ application/notice/ application/link/
|
||||
<BasicForm @register="registerSearch" />
|
||||
<BasicTable @register="registerTable">
|
||||
<template #tableTitle>
|
||||
<el-button type="primary" @click="handleAdd">新增链接</el-button>
|
||||
<a-button type="primary" @click="handleAdd">新增链接</a-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</PageWrapper>
|
||||
|
||||
@@ -146,7 +146,7 @@ class ConfigWeb(BaseModel):
|
||||
| 用途 | 枚举值(下拉选项) | 系统参数(配置值) |
|
||||
| 值类型 | 简单值(字符串) | 可存储较长的配置内容 |
|
||||
| 典型场景 | 状态、性别、类型 | SMTP配置、站点信息 |
|
||||
| 前端用法 | el-select 选项 | 配置表单 |
|
||||
| 前端用法 | a-select 选项 | 配置表单 |
|
||||
| 缓存策略 | Redis 缓存 | 按需查询 |
|
||||
| 数据量 | 通常每个字典 < 50 项 | 每个分组 < 20 项 |
|
||||
|
||||
@@ -158,18 +158,18 @@ class ConfigWeb(BaseModel):
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-form-item :label="item.name">
|
||||
<a-form-item :label="item.name">
|
||||
<!-- 文本输入 -->
|
||||
<el-input v-if="item.type === 'text'" v-model="item.value" />
|
||||
<a-input v-if="item.type === 'text'" v-model:value="item.value" />
|
||||
<!-- 数字输入 -->
|
||||
<el-input-number v-else-if="item.type === 'number'" v-model="item.value" />
|
||||
<a-input-number v-else-if="item.type === 'number'" v-model:value="item.value" />
|
||||
<!-- 下拉选择 -->
|
||||
<el-select v-else-if="item.type === 'select'" v-model="item.value">
|
||||
<el-option v-for="opt in parseOptions(item.options)" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</el-select>
|
||||
<a-select v-else-if="item.type === 'select'" v-model:value="item.value">
|
||||
<a-select-option v-for="opt in parseOptions(item.options)" :key="opt.value" :value="opt.value">{{ opt.label }}</a-select-option>
|
||||
</a-select>
|
||||
<!-- 开关 -->
|
||||
<el-switch v-else-if="item.type === 'switch'" v-model="item.value" />
|
||||
</el-form-item>
|
||||
<a-switch v-else-if="item.type === 'switch'" v-model:checked="item.value" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
```
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ if DJANGO_DEMO:
|
||||
前端可通过响应中的 `msg` 字段识别演示模式拦截,并展示相应提示:
|
||||
|
||||
```typescript
|
||||
// 前端拦截器已统一处理 code=1 的响应,自动弹出 ElMessage.error(msg)
|
||||
// 前端拦截器已统一处理 code=1 的响应,自动弹出 message.error(msg)
|
||||
// 无需额外处理,演示模式的提示会自动显示
|
||||
```
|
||||
|
||||
|
||||
@@ -87,14 +87,15 @@ def _enrich_records(records):
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-select v-model="form.trainingMethod" placeholder="请选择培训方式">
|
||||
<el-option
|
||||
<a-select v-model:value="form.trainingMethod" placeholder="请选择培训方式">
|
||||
<a-select-option
|
||||
v-for="item in dictOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
>
|
||||
{{ item.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -116,7 +117,7 @@ onMounted(async () => {
|
||||
|
||||
```typescript
|
||||
import { h } from 'vue';
|
||||
import { ElTag } from 'element-plus';
|
||||
import { Tag } from 'ant-design-vue';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
@@ -125,8 +126,8 @@ export const columns = [
|
||||
render(record) {
|
||||
const map = { 1: '线上', 2: '线下', 3: '混合' };
|
||||
const typeMap = { 1: 'success', 2: 'warning', 3: 'info' };
|
||||
return h(ElTag, { type: typeMap[record.row.trainingMethod] }, {
|
||||
default: () => map[record.row.trainingMethod] || '-'
|
||||
return h(Tag, { color: typeMap[record.trainingMethod] }, {
|
||||
default: () => map[record.trainingMethod] || '-'
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -105,19 +105,20 @@ const statusOptions = dictStore.getDict('sys_status')
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<!-- el-select 下拉选择 -->
|
||||
<el-select v-model="form.status" placeholder="请选择状态">
|
||||
<el-option
|
||||
<!-- a-select 下拉选择 -->
|
||||
<a-select v-model:value="form.status" placeholder="请选择状态">
|
||||
<a-select-option
|
||||
v-for="item in statusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
>
|
||||
{{ item.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<!-- el-tag 标签展示 -->
|
||||
<el-tag v-if="form.status === '1'">正常</el-tag>
|
||||
<el-tag v-else type="danger">停用</el-tag>
|
||||
<!-- Tag 标签展示 -->
|
||||
<Tag v-if="form.status === '1'" color="success">正常</Tag>
|
||||
<Tag v-else color="error">停用</Tag>
|
||||
</template>
|
||||
```
|
||||
|
||||
@@ -174,7 +175,7 @@ export const useDictStore = defineStore('dict', {
|
||||
|
||||
```
|
||||
1. 两级结构:字典类型(分组)+ 字典项(具体值),清晰管理
|
||||
2. 前端集成:useDictStore 统一管理,el-select 直接绑定
|
||||
2. 前端集成:useDictStore 统一管理,a-select 直接绑定
|
||||
3. 值为字符串:字典项 value 统一为字符串类型
|
||||
4. 避免硬编码:枚举值统一走字典,修改无需改代码
|
||||
5. Redis 缓存:减少数据库查询,提升性能
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useLockFn } from '@/utils/lock'
|
||||
// 包装提交函数,防止重复点击
|
||||
const handleSubmit = useLockFn(async () => {
|
||||
await axios.post('/level/add', formData)
|
||||
ElMessage.success('添加成功')
|
||||
message.success('添加成功')
|
||||
})
|
||||
```
|
||||
|
||||
@@ -37,25 +37,26 @@ function useLockFn(fn) {
|
||||
|
||||
### 按钮 loading
|
||||
|
||||
ElementPlus 的按钮组件支持 `loading` 属性,请求期间禁用按钮:
|
||||
Ant Design Vue 的按钮组件支持 `loading` 属性,请求期间禁用按钮:
|
||||
|
||||
```vue
|
||||
<el-button
|
||||
<a-button
|
||||
type="primary"
|
||||
:loading="submitting"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
提交
|
||||
</el-button>
|
||||
</a-button>
|
||||
|
||||
<script setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
const submitting = ref(false)
|
||||
|
||||
async function handleSubmit() {
|
||||
submitting.value = true
|
||||
try {
|
||||
await axios.post('/level/add', formData)
|
||||
ElMessage.success('添加成功')
|
||||
message.success('添加成功')
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ def check_captcha_rate_limit(ip):
|
||||
// 响应拦截器
|
||||
responseInterceptorsCatch: (error: any) => {
|
||||
if (error.response?.status === 429) {
|
||||
ElMessage.warning('请求过于频繁,请稍后再试');
|
||||
message.warning('请求过于频繁,请稍后再试');
|
||||
return Promise.reject(error);
|
||||
}
|
||||
// ... 其他错误处理
|
||||
|
||||
@@ -119,10 +119,10 @@ def get_user_permissions(user_id):
|
||||
|
||||
```html
|
||||
<!-- 只有拥有 sys:user:add 权限的用户才能看到此按钮 -->
|
||||
<el-button v-perm="'sys:user:add'" type="primary">新增</el-button>
|
||||
<a-button v-perm="'sys:user:add'" type="primary">新增</a-button>
|
||||
|
||||
<!-- 多个权限,满足其一即可 -->
|
||||
<el-button v-perm="['sys:user:update', 'sys:user:delete']">操作</el-button>
|
||||
<a-button v-perm="['sys:user:update', 'sys:user:delete']">操作</a-button>
|
||||
```
|
||||
|
||||
### 路由守卫
|
||||
|
||||
@@ -164,13 +164,13 @@ transformRequestData: (res: AxiosResponse<Result>, options: RequestOptions) => {
|
||||
|
||||
// 业务失败(code === 1)
|
||||
if (code === ResultEnum.ERROR) {
|
||||
ElMessage.error(msg);
|
||||
message.error(msg);
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
// 登录超时(code === 401)
|
||||
if (code === ResultEnum.TIMEOUT) {
|
||||
ElMessageBox.confirm('登录身份已失效,请重新登录!', '提示', { ... })
|
||||
Modal.confirm({ title: '提示', content: '登录身份已失效,请重新登录!', ... })
|
||||
.then(() => {
|
||||
storage.clear();
|
||||
window.location.href = '/login';
|
||||
@@ -214,18 +214,18 @@ const handleSubmit = async () => {
|
||||
|
||||
错误处理分两层:
|
||||
|
||||
1. **业务错误**(code !== 0):拦截器统一弹出 `ElMessage.error(msg)`,并 `throw new Error`
|
||||
1. **业务错误**(code !== 0):拦截器统一弹出 `message.error(msg)`,并 `throw new Error`
|
||||
2. **HTTP 错误**(网络异常、超时等):`responseInterceptorsCatch` 处理
|
||||
|
||||
```typescript
|
||||
responseInterceptorsCatch: (error: any) => {
|
||||
const { response, code } = error || {};
|
||||
if (code === 'ECONNABORTED') {
|
||||
ElMessage.error('接口请求超时,请刷新页面重试!');
|
||||
message.error('接口请求超时,请刷新页面重试!');
|
||||
return;
|
||||
}
|
||||
if (error.toString().includes('Network Error')) {
|
||||
ElMessageBox.confirm('请检查您的网络连接是否正常', '网络异常', { ... });
|
||||
Modal.confirm({ title: '网络异常', content: '请检查您的网络连接是否正常', ... });
|
||||
return Promise.reject(error);
|
||||
}
|
||||
checkStatus(error.response?.status, response?.data?.msg);
|
||||
|
||||
@@ -68,8 +68,8 @@ Vite 按 mode 加载环境变量,后面的文件覆盖前面的:
|
||||
|------|------|--------|------|
|
||||
| `VITE_PORT` | `number` | `8001` | 开发服务器端口 |
|
||||
| `VITE_PUBLIC_PATH` | `string` | `/` | 部署基础路径(子目录部署时修改) |
|
||||
| `VITE_GLOB_APP_TITLE` | `string` | `Django+ElementPlus版` | 应用标题(注入 HTML) |
|
||||
| `VITE_GLOB_APP_SHORT_NAME` | `string` | `Django+ElementPlus版` | 应用短名(用于生成运行时配置变量名) |
|
||||
| `VITE_GLOB_APP_TITLE` | `string` | `Django+AntDesign版` | 应用标题(注入 HTML) |
|
||||
| `VITE_GLOB_APP_SHORT_NAME` | `string` | `Django+AntDesign版` | 应用短名(用于生成运行时配置变量名) |
|
||||
| `VITE_GLOB_API_URL` | `string` | 空 | API 基础路径(留空使用相对路径) |
|
||||
| `VITE_GLOB_API_URL_PREFIX` | `string` | `/api` | API 前缀 |
|
||||
| `VITE_GLOB_FILE_URL` | `string` | - | 文件访问域名(拼接上传文件 URL) |
|
||||
@@ -177,7 +177,7 @@ import { defineStore } from 'pinia'; // 自动
|
||||
|
||||
```html
|
||||
<!-- 构建后的 index.html -->
|
||||
<title>Django+ElementPlus版</title>
|
||||
<title>Django+AntDesign版</title>
|
||||
<script src="/app.config.js?v=1.0.0-1725000000000"></script>
|
||||
```
|
||||
|
||||
@@ -192,7 +192,7 @@ window.__{SHORT_NAME}__PRODUCTION__CONF__ = {
|
||||
"VITE_GLOB_API_URL": "",
|
||||
"VITE_GLOB_API_URL_PREFIX": "/api",
|
||||
"VITE_GLOB_FILE_URL": "http://file.django.elevue",
|
||||
"VITE_GLOB_APP_TITLE": "Django+ElementPlus版",
|
||||
"VITE_GLOB_APP_TITLE": "Django+AntDesign版",
|
||||
// ...
|
||||
};
|
||||
Object.freeze(window.__{SHORT_NAME}__PRODUCTION__CONF__);
|
||||
@@ -256,7 +256,7 @@ dist/
|
||||
├── app.config.js # 运行时配置(VITE_GLOB_* 变量)
|
||||
├── favicon.ico # 网站图标
|
||||
└── assets/
|
||||
├── index-[hash].js # 主包(Vue、ElementPlus、Pinia 等)
|
||||
├── index-[hash].js # 主包(Vue、Ant Design Vue、Pinia 等)
|
||||
├── index-[hash].css # 全局样式
|
||||
├── [name]-[hash].js # 路由懒加载 chunk(按视图分割)
|
||||
└── ... # 字体、图片等静态资源
|
||||
@@ -283,7 +283,7 @@ Vite 自动将 `node_modules` 中的库分割为独立 chunk:
|
||||
| chunk | 内容 |
|
||||
|-------|------|
|
||||
| `vendor-vue.js` | vue + vue-router + pinia |
|
||||
| `vendor-element.js` | element-plus |
|
||||
| `vendor-antd.js` | ant-design-vue |
|
||||
| `vendor-echarts.js` | echarts(如果使用) |
|
||||
| `vendor-*.js` | 其他大型依赖 |
|
||||
|
||||
@@ -293,7 +293,7 @@ Vite 自动将 `node_modules` 中的库分割为独立 chunk:
|
||||
|
||||
```typescript
|
||||
optimizeDeps: {
|
||||
include: ['dayjs', '@vicons/ionicons5', '@vicons/antd', '@element-plus/icons-vue'],
|
||||
include: ['dayjs', '@ant-design/icons-vue'],
|
||||
}
|
||||
```
|
||||
|
||||
@@ -307,13 +307,8 @@ optimizeDeps: {
|
||||
async function bootstrap() {
|
||||
const app = createApp(App);
|
||||
|
||||
// 1. 全局注册 ElementPlus 图标
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component);
|
||||
}
|
||||
|
||||
// 2. 全局引入 ElementPlus(中文包)
|
||||
setupElement(app);
|
||||
// 1. 全局注册 Ant Design Vue 组件
|
||||
setupAntd(app);
|
||||
|
||||
// 3. 全局注册自定义组件(BasicTable、BasicForm 等)
|
||||
setupCustomComponents(app);
|
||||
@@ -339,7 +334,7 @@ async function bootstrap() {
|
||||
|
||||
| 注册项 | 文件 | 说明 |
|
||||
|--------|------|------|
|
||||
| ElementPlus | `element.ts` | 全局引入 + 中文包 |
|
||||
| Ant Design Vue | `antdv.ts` | 全局引入组件 |
|
||||
| 自定义组件 | `customComponents.ts` | BasicTable、BasicForm、PageWrapper 等 |
|
||||
| 自定义指令 | `directives.ts` | `v-perm`、`v-perms`、`v-scrollBar` |
|
||||
|
||||
@@ -504,7 +499,7 @@ pnpm report
|
||||
**优化手段**:
|
||||
|
||||
1. 路由懒加载(已默认启用)
|
||||
2. 按需引入 ElementPlus 组件(当前为全量引入)
|
||||
2. 按需引入 Ant Design Vue 组件(当前为全量引入)
|
||||
3. 大型依赖(echarts、wangeditor)按需引入
|
||||
4. 开启 gzip 压缩(`VITE_BUILD_COMPRESS=gzip`)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- Copyright (c) 2021-2026 小蚂蚁云团队 版权所有 -->
|
||||
|
||||
:::tip 说明
|
||||
项目提供丰富的公共组件,位于 `ui/src/components/` 目录,基于 ElementPlus 二次封装,覆盖表格、表单、弹窗、上传、富文本编辑等常见场景。组件通过 `src/plugins/` 中的 `setupCustomComponents` 全局注册,任何页面直接使用,无需单独 import。
|
||||
项目提供丰富的公共组件,位于 `ui/src/components/` 目录,基于 Ant Design Vue 二次封装,覆盖表格、表单、弹窗、上传、富文本编辑等常见场景。组件通过 `src/plugins/` 中的 `setupCustomComponents` 全局注册,任何页面直接使用,无需单独 import。
|
||||
:::
|
||||
|
||||
## 组件总览
|
||||
@@ -19,7 +19,7 @@
|
||||
| CropperImage | `components/Cropper/` | 图片裁剪(圆形/矩形、实时预览) |
|
||||
| QrCode | `components/Qrcode/` | 二维码生成(canvas/img、内嵌 logo) |
|
||||
| ChinaArea | `components/ChinaArea/` | 省市区级联(懒加载、二/三级联动) |
|
||||
| Icon | `components/icon/` | 图标(支持 ElementPlus 图标 + SVG 图标) |
|
||||
| Icon | `components/icon/` | 图标(支持 Ant Design Icons + SVG 图标) |
|
||||
| BasicSelect | `components/Select/` | 增强下拉(远程请求、本地缓存) |
|
||||
| CountTo | `components/CountTo/` | 数字动画(前缀/后缀、千分位、缓动曲线) |
|
||||
| Password | `components/Password/` | 密码强度(强度指示器、重复确认、复杂度校验) |
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
## BasicTable 表格
|
||||
|
||||
表格组件是使用频率最高的组件,基于 `el-table` + `el-pagination` 封装,详见 [BasicTable 表格](/zh/volume4/frontend/table) 章节。
|
||||
表格组件是使用频率最高的组件,基于 Ant Design Vue 的 `a-table` 封装,详见 [BasicTable 表格](/zh/volume4/frontend/table) 章节。
|
||||
|
||||
### Props
|
||||
|
||||
@@ -79,12 +79,12 @@
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `label` | `string` | 列标题 |
|
||||
| `prop` | `string` | 字段名 |
|
||||
| `title` | `string` | 列标题 |
|
||||
| `dataIndex` | `string` | 字段名 |
|
||||
| `width` | `number` | 列宽 |
|
||||
| `align` | `string` | 对齐方式 |
|
||||
| `type` | `string` | 类型:`index` / `selection` |
|
||||
| `render` | `Function` | 自定义渲染函数 `(column, row, index) => VNode` |
|
||||
| `customRender` | `Function` | 自定义渲染函数 `({ text, record, index, column }) => VNode` |
|
||||
| `isSlot` | `boolean` | 是否使用插槽 |
|
||||
| `ellipsis` | `boolean` | 超出省略(tooltip) |
|
||||
| `auth` | `string[]` | 权限编码,控制列是否显示 |
|
||||
@@ -131,37 +131,42 @@
|
||||
<template>
|
||||
<BasicTable :columns="columns" :request="loadDataTable" ref="tableRef">
|
||||
<template #tableTitle>
|
||||
<el-button v-perm="['sys:level:add']" type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>新增
|
||||
</el-button>
|
||||
<a-space>
|
||||
<a-button v-perm="['sys:level:add']" type="primary" @click="handleAdd">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新增
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleEdit(record.id)" v-perm="['sys:level:update']">
|
||||
<template #icon><EditOutlined /></template>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="primary" danger @click="handleDelete(record.id)" v-perm="['sys:level:delete']">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const columns = [
|
||||
{ label: '名称', prop: 'name', width: 200 },
|
||||
{ label: '状态', prop: 'status', width: 100 },
|
||||
{ label: '创建时间', prop: 'createTime', width: 180 },
|
||||
{ title: '名称', dataIndex: 'name', width: 200 },
|
||||
{ title: '状态', dataIndex: 'status', width: 100 },
|
||||
{ title: '创建时间', dataIndex: 'createTime', width: 180 },
|
||||
];
|
||||
|
||||
const actionColumn = {
|
||||
width: 200,
|
||||
actions: [
|
||||
{ label: '编辑', auth: ['sys:level:update'], onClick: handleEdit },
|
||||
{
|
||||
label: '删除',
|
||||
auth: ['sys:level:delete'],
|
||||
isConfirm: true,
|
||||
popConfirm: { title: '确认删除?' },
|
||||
onClick: handleDelete,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
async function loadDataTable({ page, pageSize }) {
|
||||
const res = await getListApi({ pageNo: page, pageSize });
|
||||
return { items: res.data, total: res.count };
|
||||
async function loadDataTable(params) {
|
||||
const res = await getListApi(params);
|
||||
return res;
|
||||
}
|
||||
</script>
|
||||
```
|
||||
@@ -228,7 +233,7 @@ async function loadDataTable({ page, pageSize }) {
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `field` | `string` | 字段名(必填) |
|
||||
| `name` | `string` | 字段名(必填) |
|
||||
| `label` | `string` | 标签文字 |
|
||||
| `component` | `ComponentType` | 渲染组件:`Input` / `Select` / `DatePicker` 等 |
|
||||
| `componentProps` | `object` | 组件属性 |
|
||||
@@ -270,13 +275,13 @@ async function loadDataTable({ page, pageSize }) {
|
||||
const [registerForm] = useForm({
|
||||
schemas: [
|
||||
{
|
||||
field: 'name',
|
||||
name: 'name',
|
||||
label: '名称',
|
||||
component: 'Input',
|
||||
componentProps: { placeholder: '请输入名称' },
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
name: 'status',
|
||||
label: '状态',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
@@ -287,7 +292,7 @@ const [registerForm] = useForm({
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'dateRange',
|
||||
name: 'dateRange',
|
||||
label: '日期范围',
|
||||
component: 'DatePicker',
|
||||
componentProps: { type: 'daterange' },
|
||||
@@ -301,7 +306,7 @@ const [registerForm] = useForm({
|
||||
|
||||
## BasicModal 弹窗
|
||||
|
||||
弹窗组件基于 `el-dialog` 封装,支持拖拽、自定义按钮,详见 [Modal 弹窗](/zh/volume4/frontend/modal) 章节。
|
||||
弹窗组件基于 `a-modal` 封装,支持拖拽、自定义按钮,详见 [Modal 弹窗](/zh/volume4/frontend/modal) 章节。
|
||||
|
||||
### Props
|
||||
|
||||
@@ -358,7 +363,7 @@ function handleEdit(record) {
|
||||
|
||||
## BasicUpload 上传
|
||||
|
||||
文件/图片上传组件,基于 `el-upload` 封装,支持卡片/列表模式、数量限制、图片预览。
|
||||
文件/图片上传组件,基于 Ant Design Vue 的 `a-upload` 封装,支持卡片/列表模式、数量限制、图片预览。
|
||||
|
||||
### Props
|
||||
|
||||
@@ -700,10 +705,10 @@ async function getDeptOptions() {
|
||||
<BasicTable :columns="columns" :request="loadData" />
|
||||
|
||||
<template #leftFooter>
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
<a-button @click="handleCancel">取消</a-button>
|
||||
</template>
|
||||
<template #rightFooter>
|
||||
<el-button type="primary" @click="handleSubmit">提交</el-button>
|
||||
<a-button type="primary" @click="handleSubmit">提交</a-button>
|
||||
</template>
|
||||
</PageWrapper>
|
||||
</template>
|
||||
@@ -726,7 +731,7 @@ async function getDeptOptions() {
|
||||
```vue
|
||||
<template>
|
||||
<Authority :value="['sys:user:add']">
|
||||
<el-button type="primary">新增用户</el-button>
|
||||
<a-button type="primary">新增用户</a-button>
|
||||
</Authority>
|
||||
</template>
|
||||
```
|
||||
@@ -734,7 +739,7 @@ async function getDeptOptions() {
|
||||
:::tip v-perm 指令
|
||||
除了 `Authority` 组件,项目还提供 `v-perm` 指令,功能相同但更简洁:
|
||||
```vue
|
||||
<el-button v-perm="['sys:user:add']">新增用户</el-button>
|
||||
<a-button v-perm="['sys:user:add']">新增用户</a-button>
|
||||
```
|
||||
:::
|
||||
|
||||
@@ -742,7 +747,7 @@ async function getDeptOptions() {
|
||||
|
||||
## Icon 图标
|
||||
|
||||
统一图标组件,支持 ElementPlus 图标和 SVG 图标。
|
||||
统一图标组件,支持 Ant Design Icons 和 SVG 图标。
|
||||
|
||||
### Props
|
||||
|
||||
@@ -756,8 +761,8 @@ async function getDeptOptions() {
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<!-- ElementPlus 图标 -->
|
||||
<Icon name="el-icon-Plus" size="18px" />
|
||||
<!-- Ant Design Icons -->
|
||||
<PlusOutlined style="font-size: 18px" />
|
||||
<!-- SVG 图标 -->
|
||||
<Icon name="svg-icon-user" />
|
||||
</template>
|
||||
@@ -838,4 +843,4 @@ setupCustomComponents(app); // 注册所有自定义组件
|
||||
|
||||
## 总结
|
||||
|
||||
公共组件库基于 ElementPlus 二次封装,核心三件套 BasicTable(表格)、BasicForm(表单)、BasicModal(弹窗)覆盖列表页标准场景。辅以 Upload、Editor、Cropper 等业务组件,以及 Authority、v-perm 权限体系。通过全局注册,任何页面直接使用,无需单独 import。优先使用公共组件,避免重复开发。
|
||||
公共组件库基于 Ant Design Vue 二次封装,核心三件套 BasicTable(表格)、BasicForm(表单)、BasicModal(弹窗)覆盖列表页标准场景。辅以 Upload、Editor、Cropper 等业务组件,以及 Authority、v-perm 权限体系。通过全局注册,任何页面直接使用,无需单独 import。优先使用公共组件,避免重复开发。
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- Copyright (c) 2021-2026 小蚂蚁云团队 版权所有 -->
|
||||
|
||||
:::tip 说明
|
||||
`BasicForm` 位于 `src/components/Form/src/BasicForm.vue`,基于 ElementPlus 的 `el-form` 二次封装,通过 schemas 配置化定义表单字段,支持查询表单和业务表单两种模式。
|
||||
`BasicForm` 位于 `src/components/Form/src/BasicForm.vue`,基于 Ant Design Vue 的 `a-form` 二次封装,通过 schemas 配置化定义表单字段,支持查询表单和业务表单两种模式。
|
||||
:::
|
||||
|
||||
## 组件架构
|
||||
@@ -26,9 +26,9 @@ src/components/Form/
|
||||
列表页的搜索区域使用 `useForm` 注册表单:
|
||||
|
||||
```vue
|
||||
<el-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||
<a-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" />
|
||||
</el-card>
|
||||
</a-card>
|
||||
```
|
||||
|
||||
### querySchemas 定义
|
||||
@@ -40,7 +40,7 @@ import { FormSchema } from '@/components/Form/index';
|
||||
|
||||
export const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'name',
|
||||
name: 'name',
|
||||
component: 'Input',
|
||||
label: '岗位名称',
|
||||
componentProps: {
|
||||
@@ -48,12 +48,13 @@ export const schemas: FormSchema[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
name: 'status',
|
||||
component: 'Select',
|
||||
label: '状态',
|
||||
componentProps: {
|
||||
placeholder: '请选择状态',
|
||||
clearable: true,
|
||||
allowClear: true,
|
||||
options: [
|
||||
{ label: '正常', value: '1' },
|
||||
{ label: '禁用', value: '2' },
|
||||
@@ -68,15 +69,19 @@ export const schemas: FormSchema[] = [
|
||||
```typescript
|
||||
import { schemas } from './querySchemas';
|
||||
import { useForm } from '@/components/Form/index';
|
||||
import { ColProps } from 'element-plus';
|
||||
|
||||
const formParams = reactive({ name: '', status: '' });
|
||||
|
||||
const [register, {}] = useForm({
|
||||
labelWidth: 80,
|
||||
layout: 'horizontal',
|
||||
colProps: { span: 6 } as ColProps,
|
||||
submitOnReset: true,
|
||||
rowProps: { gutter: [16, 0] },
|
||||
colProps: {
|
||||
xs: 24,
|
||||
sm: 24,
|
||||
md: 12,
|
||||
lg: 8,
|
||||
xl: 6,
|
||||
},
|
||||
labelCol: { span: 6, offset: 0 },
|
||||
schemas,
|
||||
});
|
||||
|
||||
@@ -95,73 +100,73 @@ function handleReset() {
|
||||
|
||||
## 业务表单(编辑弹窗)
|
||||
|
||||
编辑弹窗中直接使用 `el-form` + `el-form-item` 构建表单:
|
||||
编辑弹窗中直接使用 `a-form` + `a-form-item` 构建表单:
|
||||
|
||||
```vue
|
||||
<!-- src/views/system/position/edit.vue -->
|
||||
<template>
|
||||
<el-dialog v-model="props.visible" :title="props.positionId ? '编辑' : '新增'" width="500">
|
||||
<el-form class="ls-form" ref="formRef" :model="formData" label-width="80px">
|
||||
<el-form-item
|
||||
<a-modal v-model:visible="props.visible" :title="props.positionId ? '编辑' : '新增'" width="500px" @cancel="dialogClose">
|
||||
<a-form class="ls-form" ref="formRef" :model="formData" :label-col="{ style: { width: '85px' } }">
|
||||
<a-form-item
|
||||
label="岗位名称"
|
||||
prop="name"
|
||||
name="name"
|
||||
:rules="{ required: true, message: '请输入岗位名称', trigger: 'blur' }"
|
||||
>
|
||||
<el-input v-model="formData.name" placeholder="请输入岗位名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="岗位状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :value="1">正常</el-radio>
|
||||
<el-radio :value="2">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="formData.sort" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
<a-input v-model:value="formData.name" placeholder="请输入岗位名称" allow-clear />
|
||||
</a-form-item>
|
||||
<a-form-item label="岗位状态" name="status">
|
||||
<a-radio-group v-model:value="formData.status" name="status">
|
||||
<a-radio :value="1">正常</a-radio>
|
||||
<a-radio :value="2">停用</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" name="sort">
|
||||
<a-input-number v-model:value="formData.sort" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
```
|
||||
|
||||
## BasicForm 内部渲染机制
|
||||
|
||||
`BasicForm.vue` 根据 `schema.component` 动态渲染对应的 ElementPlus 组件:
|
||||
`BasicForm.vue` 根据 `schema.component` 动态渲染对应的 Ant Design Vue 组件:
|
||||
|
||||
```vue
|
||||
<!-- src/components/Form/src/BasicForm.vue 核心逻辑 -->
|
||||
<template v-for="schema in getSchema" :key="schema.field">
|
||||
<template v-for="schema in getSchema" :key="schema.name">
|
||||
<!-- Checkbox -->
|
||||
<template v-if="schema.component === 'Checkbox'">
|
||||
<el-checkbox-group v-model="formModel[schema.field]">
|
||||
<el-checkbox v-for="item in schema.componentProps?.options" :value="item.value">
|
||||
{{ item.label }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<a-checkbox-group v-bind="getSpecComponentProps(schema)" v-model:value="formModel[schema.name]">
|
||||
<a-space>
|
||||
<a-checkbox v-for="item in schema.componentProps?.options" :key="item.value" :value="item.value" :label="item.label" />
|
||||
</a-space>
|
||||
</a-checkbox-group>
|
||||
</template>
|
||||
|
||||
<!-- RadioGroup -->
|
||||
<template v-else-if="schema.component === 'RadioGroup'">
|
||||
<el-radio-group v-model="formModel[schema.field]">
|
||||
<el-radio v-for="item in schema.componentProps?.options" :value="item.value">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<a-radio-group v-bind="getSpecComponentProps(schema)" v-model:value="formModel[schema.name]">
|
||||
<a-space>
|
||||
<a-radio v-for="item in schema.componentProps?.options" :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</a-radio>
|
||||
</a-space>
|
||||
</a-radio-group>
|
||||
</template>
|
||||
|
||||
<!-- Select -->
|
||||
<template v-else-if="schema.component === 'Select'">
|
||||
<el-select v-model="formModel[schema.field]">
|
||||
<el-option v-for="item in schema.componentProps?.options" :value="item.value" :label="item.label" />
|
||||
</el-select>
|
||||
<!-- BasicSelect -->
|
||||
<template v-else-if="schema.component === 'BasicSelect'">
|
||||
<BasicSelect v-model:value="formModel[schema.name]" v-bind="schema.componentProps" />
|
||||
</template>
|
||||
|
||||
<!-- Daterange -->
|
||||
<template v-else-if="schema.component === 'Daterange'">
|
||||
<el-date-picker v-model="formModel[schema.field]" type="daterange" value-format="YYYY-MM-DD" />
|
||||
</template>
|
||||
|
||||
<!-- 其他组件:动态渲染 el-{component} -->
|
||||
<component v-else :is="`el-${schema.component}`" v-model="formModel[schema.field]" />
|
||||
<!-- 其他组件:动态渲染 a-{component} -->
|
||||
<component
|
||||
v-else
|
||||
v-bind="getComponentProps(schema)"
|
||||
:is="`a-${schema.component}`"
|
||||
v-model:value="formModel[schema.name]"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
@@ -169,10 +174,10 @@ function handleReset() {
|
||||
|
||||
```typescript
|
||||
interface FormSchema {
|
||||
field: string; // 字段名
|
||||
name: string; // 字段名
|
||||
label: string; // 标签文本
|
||||
component: string; // 组件类型:Input / Select / RadioGroup / Checkbox / DatePicker 等
|
||||
componentProps?: any; // 组件属性(placeholder、options、clearable 等)
|
||||
componentProps?: any; // 组件属性(placeholder、options、allowClear 等)
|
||||
required?: boolean; // 是否必填
|
||||
rules?: any[]; // 自定义校验规则
|
||||
defaultValue?: any; // 默认值
|
||||
@@ -202,7 +207,7 @@ Schema 中的 `required: true` 会自动生成必填校验规则,也可以通
|
||||
|
||||
```typescript
|
||||
{
|
||||
field: 'name',
|
||||
name: 'name',
|
||||
label: '岗位名称',
|
||||
component: 'Input',
|
||||
rules: [
|
||||
@@ -239,4 +244,4 @@ onMounted(() => {
|
||||
|
||||
## 总结
|
||||
|
||||
BasicForm 通过 schemas 配置化定义表单字段,内部根据 `component` 动态渲染对应的 ElementPlus 组件。查询表单使用 `useForm` 注册并绑定 `@submit`/`@reset` 事件,业务表单直接使用 `el-form` + `reactive` 管理数据。支持多种组件类型、表单校验、数据绑定、条件隐藏等能力。
|
||||
BasicForm 通过 schemas 配置化定义表单字段,内部根据 `component` 动态渲染对应的 Ant Design Vue 组件。查询表单使用 `useForm` 注册并绑定 `@submit`/`@reset` 事件,业务表单直接使用 `a-form` + `reactive` 管理数据。支持多种组件类型、表单校验、数据绑定、条件隐藏等能力。
|
||||
|
||||
@@ -132,7 +132,7 @@ const { isLock: subLoading, lockFn: submit } = useLockFn(handleSubmit);
|
||||
```
|
||||
|
||||
```vue
|
||||
<el-button :loading="subLoading" type="primary" @click="submit">确定</el-button>
|
||||
<a-button :loading="subLoading" type="primary" @click="submit">确定</a-button>
|
||||
```
|
||||
|
||||
## useAsync
|
||||
|
||||
@@ -3,39 +3,36 @@
|
||||
<!-- Copyright (c) 2021-2026 小蚂蚁云团队 版权所有 -->
|
||||
|
||||
:::tip 说明
|
||||
当前项目仅支持中文界面,ElementPlus 使用中文语言包(`zh-cn`),所有界面文本直接硬编码在组件中。如需扩展多语言支持,可基于 vue-i18n 实现。
|
||||
当前项目仅支持中文界面,Ant Design Vue 默认支持中文,所有界面文本直接硬编码在组件中。如需扩展多语言支持,可基于 vue-i18n 实现。
|
||||
:::
|
||||
|
||||
## 当前状态
|
||||
|
||||
项目当前为中文单语言版本:
|
||||
|
||||
- ElementPlus 全局配置中文语言包:`app.use(ElementPlus, { locale: zhCn })`
|
||||
- 所有界面文本直接硬编码在组件中:`<el-button>添加岗位</el-button>`
|
||||
- Ant Design Vue 默认支持中文,无需额外配置语言包
|
||||
- 所有界面文本直接硬编码在组件中:`<a-button>添加岗位</a-button>`
|
||||
- 后端返回的 `msg` 字段为中文:`"操作成功"`、`"岗位名称不能重复"`
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-button type="primary">添加岗位</el-button>
|
||||
<el-table-column label="岗位名称" prop="name" />
|
||||
<el-table-column label="创建时间" prop="createTime" />
|
||||
<a-button type="primary">添加岗位</a-button>
|
||||
<!-- 表格列通过 columns.ts 配置 -->
|
||||
</template>
|
||||
```
|
||||
|
||||
## ElementPlus 中文配置
|
||||
## Ant Design Vue 中文配置
|
||||
|
||||
项目在入口文件中全局配置 ElementPlus 中文语言包:
|
||||
Ant Design Vue 默认支持中文,无需额外配置。如需切换语言,可通过 ConfigProvider 设置:
|
||||
|
||||
```typescript
|
||||
// main.ts
|
||||
import ElementPlus from 'element-plus';
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
||||
|
||||
app.use(ElementPlus, { locale: zhCn });
|
||||
```vue
|
||||
<template>
|
||||
<a-config-provider :locale="zhCN">
|
||||
<App />
|
||||
</a-config-provider>
|
||||
</template>
|
||||
```
|
||||
|
||||
配置后,ElementPlus 组件的默认文本(如分页、日期选择器、表格空数据等)自动显示中文。
|
||||
|
||||
## 扩展方案
|
||||
|
||||
如需支持多语言,推荐使用 vue-i18n:
|
||||
@@ -126,8 +123,8 @@ export default i18n;
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-button type="primary">{{ $t('common.add') }}</el-button>
|
||||
<el-table-column :label="$t('position.name')" prop="name" />
|
||||
<a-button type="primary">{{ $t('common.add') }}</a-button>
|
||||
<!-- 表格列通过 columns.ts 配置 -->
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -136,12 +133,12 @@ const { t } = useI18n();
|
||||
</script>
|
||||
```
|
||||
|
||||
### ElementPlus 国际化联动
|
||||
### Ant Design Vue 国际化联动
|
||||
|
||||
```typescript
|
||||
// 动态切换 ElementPlus 语言
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
||||
import en from 'element-plus/es/locale/lang/en';
|
||||
// 动态切换 Ant Design Vue 语言
|
||||
import zhCN from 'ant-design-vue/es/locale/zh_CN';
|
||||
import enUS from 'ant-design-vue/es/locale/en_US';
|
||||
|
||||
const locale = computed(() => {
|
||||
return i18n.global.locale.value === 'zh-CN' ? zhCn : en;
|
||||
@@ -150,9 +147,9 @@ const locale = computed(() => {
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-config-provider :locale="locale">
|
||||
<a-config-provider :locale="locale">
|
||||
<App />
|
||||
</el-config-provider>
|
||||
</a-config-provider>
|
||||
</template>
|
||||
```
|
||||
|
||||
@@ -163,7 +160,7 @@ const locale = computed(() => {
|
||||
function changeLanguage(lang: string) {
|
||||
i18n.global.locale.value = lang;
|
||||
localStorage.setItem('lang', lang);
|
||||
// 同步切换 ElementPlus 语言
|
||||
// 同步切换 Ant Design Vue 语言
|
||||
}
|
||||
```
|
||||
|
||||
@@ -206,7 +203,7 @@ const errorMap = {
|
||||
```
|
||||
|
||||
:::warning 注意事项
|
||||
1. ElementPlus 内置组件的文本(如"暂无数据"、"请选择")通过 `locale` 属性自动切换
|
||||
1. Ant Design Vue 内置组件的文本(如"暂无数据"、"请选择")通过 `locale` 属性自动切换
|
||||
2. 后端返回的 `msg` 字段需要额外处理
|
||||
3. 日期格式、数字格式也需要考虑国际化
|
||||
:::
|
||||
@@ -227,4 +224,4 @@ const errorMap = {
|
||||
|
||||
## 总结
|
||||
|
||||
当前项目为中文单语言版本,ElementPlus 使用中文语言包。如需扩展多语言支持,可基于 vue-i18n 实现前端国际化,配合 ElementPlus 内置语言包和后端消息 code 实现全链路多语言。改造需考虑前端文本、ElementPlus 组件、后端消息三个维度的国际化。
|
||||
当前项目为中文单语言版本,Ant Design Vue 默认支持中文。如需扩展多语言支持,可基于 vue-i18n 实现前端国际化,配合 Ant Design Vue 内置语言包和后端消息 code 实现全链路多语言。改造需考虑前端文本、Ant Design Vue 组件、后端消息三个维度的国际化。
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- Copyright (c) 2021-2026 小蚂蚁云团队 版权所有 -->
|
||||
|
||||
:::tip 说明
|
||||
项目提供 `BasicModal` 组件(`src/components/Modal/src/BasicModal.vue`)和原生 `el-dialog` 两种弹窗用法。编辑弹窗通过 `defineAsyncComponent` 懒加载,减少首屏渲染负担。
|
||||
项目提供 `BasicModal` 组件(`src/components/Modal/src/basicModal.vue`)和原生 `a-modal` 两种弹窗用法。编辑弹窗通过 `defineAsyncComponent` 懒加载,减少首屏渲染负担。
|
||||
:::
|
||||
|
||||
## 组件架构
|
||||
@@ -11,20 +11,24 @@
|
||||
```
|
||||
src/components/Modal/
|
||||
├── src/
|
||||
│ ├── BasicModal.vue # 主组件(基于 el-dialog)
|
||||
│ ├── basicModal.vue # 主组件(基于 a-modal)
|
||||
│ ├── props.ts # 属性定义
|
||||
│ ├── type.ts # 类型定义(ModalProps、ModalMethods)
|
||||
│ └── hooks/
|
||||
│ └── useModalContext.ts # 弹窗上下文
|
||||
│ ├── type/index.ts # 类型定义(ModalProps、ModalMethods)
|
||||
│ ├── hooks/
|
||||
│ │ ├── useModal.ts # 弹窗 Hook
|
||||
│ │ └── useModalDrag.ts # 弹窗拖拽
|
||||
│ └── components/
|
||||
│ ├── Modal.tsx # 弹窗渲染组件
|
||||
│ └── ModalClose.vue # 关闭按钮组件
|
||||
└── index.ts # 导出 useModal 等
|
||||
```
|
||||
|
||||
## BasicModal 组件
|
||||
|
||||
`BasicModal` 基于 `el-dialog` 封装,提供全屏切换、自定义头部、提交按钮等能力:
|
||||
`BasicModal` 基于 `a-modal` 封装,提供全屏切换、自定义头部、提交按钮等能力:
|
||||
|
||||
```vue
|
||||
<BasicModal :title="'编辑'" :width="600" @register="registerModal" @ok="handleSubmit">
|
||||
<BasicModal title="编辑" :width="600" @register="registerModal" @ok="handleSubmit">
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicModal>
|
||||
```
|
||||
@@ -38,7 +42,7 @@ src/components/Modal/
|
||||
|
||||
## 实际用法(position 模块)
|
||||
|
||||
以岗位管理为例,实际使用原生 `el-dialog` + `defineAsyncComponent` 懒加载:
|
||||
以岗位管理为例,实际使用原生 `a-modal` + `defineAsyncComponent` 懒加载:
|
||||
|
||||
### 父组件(index.vue)
|
||||
|
||||
@@ -59,8 +63,8 @@ const handleAdd = async () => {
|
||||
};
|
||||
|
||||
// 编辑
|
||||
const handleEdit = async (record: Recordable) => {
|
||||
positionId.value = record.row.id;
|
||||
const handleEdit = async (id: number) => {
|
||||
positionId.value = id;
|
||||
await nextTick();
|
||||
editVisible.value = true;
|
||||
};
|
||||
@@ -83,41 +87,44 @@ const handleEdit = async (record: Recordable) => {
|
||||
```vue
|
||||
<!-- src/views/system/position/edit.vue -->
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="props.visible"
|
||||
<a-modal
|
||||
v-model:visible="props.visible"
|
||||
:title="props.positionId ? '编辑' : '新增'"
|
||||
width="500"
|
||||
:close-on-click-modal="false"
|
||||
:before-close="dialogClose"
|
||||
width="500px"
|
||||
@cancel="dialogClose"
|
||||
>
|
||||
<el-form class="ls-form" ref="formRef" :model="formData" label-width="80px">
|
||||
<el-form-item
|
||||
<a-form class="ls-form" ref="formRef" :model="formData" :label-col="{ style: { width: '85px' } }">
|
||||
<a-form-item
|
||||
label="岗位名称"
|
||||
prop="name"
|
||||
name="name"
|
||||
:rules="{ required: true, message: '请输入岗位名称', trigger: 'blur' }"
|
||||
>
|
||||
<el-input v-model="formData.name" placeholder="请输入岗位名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="岗位状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :value="1">正常</el-radio>
|
||||
<el-radio :value="2">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="formData.sort" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<a-input v-model:value="formData.name" placeholder="请输入岗位名称" allow-clear />
|
||||
</a-form-item>
|
||||
<a-form-item label="岗位状态" name="status">
|
||||
<a-radio-group v-model:value="formData.status" name="status">
|
||||
<a-radio :value="1">正常</a-radio>
|
||||
<a-radio :value="2">停用</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" name="sort">
|
||||
<a-input-number v-model:value="formData.sort" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogClose">取消</el-button>
|
||||
<el-button :loading="subLoading" type="primary" @click="submit">确定</el-button>
|
||||
<span class="dialog-footer">
|
||||
<a-button @click="dialogClose">取消</a-button>
|
||||
<a-button :loading="subLoading" type="primary" @click="submit">确定</a-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from 'ant-design-vue';
|
||||
import { getPositionDetail, positionAdd, positionUpdate } from '@/api/system/position';
|
||||
import { useLockFn } from '@/utils/useLockFn';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const emit = defineEmits(['success', 'update:visible']);
|
||||
const formRef = shallowRef<FormInstance>();
|
||||
@@ -133,7 +140,7 @@ const props = defineProps({
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value?.validate();
|
||||
props.positionId ? await positionUpdate(formData) : await positionAdd(formData);
|
||||
message('操作成功');
|
||||
message.success('操作成功');
|
||||
emit('update:visible', false);
|
||||
emit('success');
|
||||
};
|
||||
@@ -163,7 +170,7 @@ onMounted(() => {
|
||||
## 弹窗数据流
|
||||
|
||||
```
|
||||
父组件点击编辑 → positionId = record.row.id → editVisible = true
|
||||
父组件点击编辑 → positionId = id → editVisible = true
|
||||
↓
|
||||
edit.vue onMounted → positionId != 0 → setFormData() → 获取详情填充表单
|
||||
↓
|
||||
@@ -174,25 +181,30 @@ emit('success') → 父组件 reloadTable() → 表格刷新
|
||||
|
||||
## 确认弹窗
|
||||
|
||||
删除等危险操作使用 `confirm` 封装函数(来自 `src/utils/auth.ts`):
|
||||
删除等危险操作使用 Ant Design Vue 的 `Modal.confirm`:
|
||||
|
||||
```typescript
|
||||
import { message, confirm } from '@/utils/auth';
|
||||
import { Modal, message } from 'ant-design-vue';
|
||||
|
||||
async function handleDelete(record: Recordable) {
|
||||
async function handleDelete(id) {
|
||||
let ids = [];
|
||||
if (!record) {
|
||||
if (!id) {
|
||||
ids = selectionData.value.map(({ id }) => id);
|
||||
}
|
||||
|
||||
await confirm('确定要删除?');
|
||||
record ? await positionDelete(record.row.id) : await positionBatchDelete(ids);
|
||||
message('删除成功');
|
||||
reloadTable();
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除?',
|
||||
onOk: async () => {
|
||||
id ? await positionDelete(id) : await positionBatchDelete(ids);
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
`confirm` 内部调用 `ElMessageBox.confirm`,返回 Promise,用户取消时抛出异常中断后续逻辑。
|
||||
`Modal.confirm` 返回一个引用,用户点击取消时不会执行 `onOk` 回调。
|
||||
|
||||
## v-model:visible 双向绑定
|
||||
|
||||
@@ -215,4 +227,4 @@ const dialogClose = () => { emit('update:visible', false); };
|
||||
|
||||
## 总结
|
||||
|
||||
编辑弹窗使用 `el-dialog` + `defineAsyncComponent` 懒加载,通过 `v-model:visible` 双向绑定显隐状态,`useLockFn` 防重复提交。父组件通过 `positionId` prop 传递编辑行 ID,子组件在 `onMounted` 中判断并加载详情数据。操作成功后 `emit('success')` 通知父组件刷新表格。
|
||||
编辑弹窗使用 `a-modal` + `defineAsyncComponent` 懒加载,通过 `v-model:visible` 双向绑定显隐状态,`useLockFn` 防重复提交。父组件通过 `positionId` prop 传递编辑行 ID,子组件在 `onMounted` 中判断并加载详情数据。操作成功后 `emit('success')` 通知父组件刷新表格。确认弹窗使用 Ant Design Vue 的 `Modal.confirm`。
|
||||
|
||||
@@ -92,15 +92,15 @@ export function usePermission() {
|
||||
### v-perm(满足任一权限)
|
||||
|
||||
```vue
|
||||
<el-button v-perm="['sys:position:add']">添加岗位</el-button>
|
||||
<el-button v-perm="['sys:position:batchDelete']">删除</el-button>
|
||||
<el-button v-perm="['sys:position:export']">导出</el-button>
|
||||
<a-button v-perm="['sys:position:add']">添加岗位</a-button>
|
||||
<a-button v-perm="['sys:position:batchDelete']">删除</a-button>
|
||||
<a-button v-perm="['sys:position:export']">导出</a-button>
|
||||
```
|
||||
|
||||
### v-perms(满足所有权限)
|
||||
|
||||
```vue
|
||||
<el-button v-perms="['sys:user:add', 'sys:user:import']">高级操作</el-button>
|
||||
<a-button v-perms="['sys:user:add', 'sys:user:import']">高级操作</a-button>
|
||||
```
|
||||
|
||||
### 与 v-if 结合
|
||||
@@ -115,7 +115,7 @@ const showAddBtn = computed(() => hasPermission(['sys:position:add']));
|
||||
```
|
||||
|
||||
```vue
|
||||
<el-button v-if="showAddBtn">添加岗位</el-button>
|
||||
<a-button v-if="showAddBtn">添加岗位</a-button>
|
||||
```
|
||||
|
||||
## 实际用法(position 模块)
|
||||
@@ -125,51 +125,41 @@ const showAddBtn = computed(() => hasPermission(['sys:position:add']));
|
||||
```vue
|
||||
<!-- src/views/system/position/index.vue -->
|
||||
<template>
|
||||
<BasicTable :columns="columns" :request="loadDataTable" ref="tableRef" :actionColumn="actionColumn">
|
||||
<BasicTable :columns="columns" :request="loadDataTable" ref="tableRef" :row-selection="{ onChange: onSelectionChange }">
|
||||
<template #tableTitle>
|
||||
<el-button type="primary" @click="handleAdd" v-perm="['sys:position:add']">
|
||||
<template #icon><el-icon><PlusOutlined /></el-icon></template>
|
||||
添加岗位
|
||||
</el-button>
|
||||
<el-button type="danger" @click="handleDelete()" :disabled="!selectionData.length"
|
||||
v-perm="['sys:position:batchDelete']">
|
||||
<template #icon><el-icon><Delete /></el-icon></template>
|
||||
删除
|
||||
</el-button>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleAdd" v-perm="['sys:position:add']">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
添加岗位
|
||||
</a-button>
|
||||
<a-button type="danger" @click="handleDelete()" :disabled="!selectionData.length"
|
||||
v-perm="['sys:position:batchDelete']">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</template>
|
||||
```
|
||||
|
||||
操作列中的权限控制通过 `auth` 属性实现:
|
||||
操作列中的权限控制通过 `bodyCell` 插槽 + `v-perm` 指令实现:
|
||||
|
||||
```typescript
|
||||
const actionColumn = reactive({
|
||||
width: 200,
|
||||
label: '操作',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
icon: 'Edit',
|
||||
type: 'warning',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
auth: ['sys:position:update'], // 权限节点
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
icon: 'Delete',
|
||||
type: 'danger',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
auth: ['sys:position:delete'], // 权限节点
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
```vue
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleEdit(record.id)" v-perm="['sys:position:update']">
|
||||
<template #icon><EditOutlined /></template>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="primary" danger @click="handleDelete(record.id)" v-perm="['sys:position:delete']">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 权限节点命名规范
|
||||
@@ -206,7 +196,7 @@ class ExampleAddView(PermissionRequired, View):
|
||||
|
||||
```vue
|
||||
<!-- 前端 -->
|
||||
<el-button v-perm="['sys:example:add']">添加案例</el-button>
|
||||
<a-button v-perm="['sys:example:add']">添加案例</a-button>
|
||||
```
|
||||
|
||||
## 超级管理员
|
||||
@@ -228,4 +218,4 @@ setupDirectives(app); // 注册 v-perm、v-perms 等指令
|
||||
|
||||
## 总结
|
||||
|
||||
`v-perm`(满足任一权限)和 `v-perms`(满足所有权限)指令通过 `usePermission` Hook 检查用户权限列表,无权限时自动从 DOM 移除元素。操作列通过 `TableAction` 组件的 `auth` 属性控制。权限节点在菜单管理中配置,需要保持前后端字符串完全一致。`*:*:*` 通配符表示超级管理员。
|
||||
`v-perm`(满足任一权限)和 `v-perms`(满足所有权限)指令通过 `usePermission` Hook 检查用户权限列表,无权限时自动从 DOM 移除元素。操作列通过 `bodyCell` 插槽 + `v-perm` 指令控制。权限节点在菜单管理中配置,需要保持前后端字符串完全一致。`*:*:*` 通配符表示超级管理员。
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- Copyright (c) 2021-2026 小蚂蚁云团队 版权所有 -->
|
||||
|
||||
:::tip 说明
|
||||
前端基于 Vue3 + ElementPlus 构建,使用 pnpm 作为包管理工具,Vite 作为构建工具,TypeScript 作为开发语言。
|
||||
前端基于 Vue3 + Ant Design Vue 构建,使用 pnpm 作为包管理工具,Vite 作为构建工具,TypeScript 作为开发语言。
|
||||
:::
|
||||
|
||||
## 技术栈
|
||||
@@ -11,7 +11,7 @@
|
||||
| 技术 | 版本 | 说明 |
|
||||
|------|------|------|
|
||||
| Vue | 3.x | 渐进式 JavaScript 框架,组合式 API |
|
||||
| ElementPlus | 2.x | Vue3 组件库 |
|
||||
| Ant Design Vue | 3.x | Vue3 组件库 |
|
||||
| Vite | 3.x | 下一代前端构建工具 |
|
||||
| Pinia | 2.x | 状态管理(Vuex 替代方案) |
|
||||
| Vue Router | 4.x | 路由管理 |
|
||||
@@ -152,7 +152,7 @@ import { setupElement, setupDirectives, setupCustomComponents } from '@/plugins'
|
||||
async function bootstrap() {
|
||||
const app = createApp(App);
|
||||
|
||||
setupElement(app); // 全局引入 ElementPlus 组件
|
||||
setupAntd(app); // 全局引入 Ant Design Vue 组件
|
||||
setupCustomComponents(app); // 注册自定义组件
|
||||
setupDirectives(app); // 注册自定义指令(v-perm 等)
|
||||
setupStore(app); // 挂载 Pinia 状态管理
|
||||
@@ -165,8 +165,8 @@ async function bootstrap() {
|
||||
void bootstrap();
|
||||
```
|
||||
|
||||
初始化顺序:样式加载 -> ElementPlus -> 自定义组件 -> 自定义指令 -> Pinia Store -> Router -> 挂载。
|
||||
初始化顺序:样式加载 -> Ant Design Vue -> 自定义组件 -> 自定义指令 -> Pinia Store -> Router -> 挂载。
|
||||
|
||||
## 总结
|
||||
|
||||
前端项目基于 Vue3 + ElementPlus + Vite + TypeScript + Pinia 构建,使用 pnpm 管理依赖,Tailwind CSS + SCSS 提供样式能力。通过 `main.ts` 的 `bootstrap()` 函数按顺序初始化所有基础设施,目录结构按功能分层组织,便于维护和扩展。
|
||||
前端项目基于 Vue3 + Ant Design Vue + Vite + TypeScript + Pinia 构建,使用 pnpm 管理依赖,Tailwind CSS + SCSS 提供样式能力。通过 `main.ts` 的 `bootstrap()` 函数按顺序初始化所有基础设施,目录结构按功能分层组织,便于维护和扩展。
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- Copyright (c) 2021-2026 小蚂蚁云团队 版权所有 -->
|
||||
|
||||
:::tip 说明
|
||||
前端样式方案采用 Tailwind CSS + SCSS + ElementPlus 主题覆盖,支持深色模式切换。全局 SCSS 变量通过 Vite 的 `additionalData` 注入。
|
||||
前端样式方案采用 Tailwind CSS + SCSS + Ant Design Vue 主题覆盖,支持深色模式切换。全局 SCSS 变量通过 Vite 的 `additionalData` 注入。
|
||||
:::
|
||||
|
||||
## 样式架构
|
||||
@@ -13,7 +13,7 @@ src/styles/
|
||||
├── var.scss # SCSS 全局变量(通过 Vite additionalData 注入)
|
||||
├── index.scss # 入口文件,引入所有样式
|
||||
├── tailwindbase.css # Tailwind CSS 基础样式
|
||||
└── element-override.css # ElementPlus 主题覆盖(可选)
|
||||
└── antd-override.css # Ant Design Vue 主题覆盖(可选)
|
||||
```
|
||||
|
||||
## SCSS 全局变量
|
||||
@@ -58,36 +58,27 @@ import './styles/tailwindbase.css';
|
||||
<div class="w-full md:w-1/2 lg:w-1/3">响应式宽度</div>
|
||||
```
|
||||
|
||||
## ElementPlus 主题
|
||||
## Ant Design Vue 主题
|
||||
|
||||
ElementPlus 通过 CSS 变量支持主题定制:
|
||||
Ant Design Vue 通过 CSS 变量和 ConfigProvider 支持主题定制:
|
||||
|
||||
```typescript
|
||||
// main.ts
|
||||
import 'element-plus/dist/index.css';
|
||||
import 'element-plus/theme-chalk/display.css';
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'; // 深色模式 CSS 变量
|
||||
```
|
||||
// src/plugins/antdv.ts
|
||||
import { Button, Form, Table, Modal, message } from 'ant-design-vue';
|
||||
|
||||
### ElementPlus 全局引入
|
||||
|
||||
```typescript
|
||||
// src/plugins/setupElement.ts
|
||||
import ElementPlus from 'element-plus';
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
||||
|
||||
export function setupElement(app) {
|
||||
app.use(ElementPlus, { locale: zhCn, size: 'default' });
|
||||
export function setupAntd(app) {
|
||||
app.use(Button).use(Form).use(Table).use(Modal);
|
||||
// ... 其他组件注册
|
||||
}
|
||||
```
|
||||
|
||||
## 深色模式
|
||||
|
||||
通过 ElementPlus 的 `dark/css-vars.css` 和 Tailwind 的 `dark` 模式实现深色主题:
|
||||
通过 Tailwind 的 `dark` 模式和 Ant Design Vue 的 ConfigProvider 实现深色主题:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-switch v-model="isDark" @change="toggleDark" />
|
||||
<a-switch v-model:checked="isDark" @change="toggleDark" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -98,15 +89,7 @@ const toggleDark = (val) => {
|
||||
</script>
|
||||
```
|
||||
|
||||
深色模式样式:
|
||||
|
||||
```css
|
||||
.dark {
|
||||
--el-bg-color: #1d1e1f;
|
||||
--el-text-color-primary: #e5eaf3;
|
||||
--el-border-color: #4c4d4f;
|
||||
}
|
||||
```
|
||||
深色模式样式通过 Ant Design Vue 的主题算法自动处理,也可通过 CSS 变量自定义。
|
||||
|
||||
## 设计配置 Store
|
||||
|
||||
@@ -132,12 +115,12 @@ export function useDesignSetting() {
|
||||
```vue
|
||||
<template>
|
||||
<PageWrapper>
|
||||
<el-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||
<a-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||
<!-- 搜索区域 -->
|
||||
</el-card>
|
||||
<el-card :bordered="false" class="proCard">
|
||||
</a-card>
|
||||
<a-card :bordered="false" class="proCard">
|
||||
<!-- 表格区域 -->
|
||||
</el-card>
|
||||
</a-card>
|
||||
</PageWrapper>
|
||||
</template>
|
||||
```
|
||||
@@ -152,4 +135,4 @@ export function useDesignSetting() {
|
||||
|
||||
## 总结
|
||||
|
||||
样式方案采用 Tailwind CSS 提供原子化样式 + SCSS 全局变量(Vite `additionalData` 注入)+ ElementPlus 主题覆盖,支持深色模式切换。`designSetting` store 管理主题配置,`PageWrapper` 组件提供统一页面容器。
|
||||
样式方案采用 Tailwind CSS 提供原子化样式 + SCSS 全局变量(Vite `additionalData` 注入)+ Ant Design Vue 主题覆盖,支持深色模式切换。`designSetting` store 管理主题配置,`PageWrapper` 组件提供统一页面容器。
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- Copyright (c) 2021-2026 小蚂蚁云团队 版权所有 -->
|
||||
|
||||
:::tip 说明
|
||||
`BasicTable` 位于 `src/components/Table/src/Table.vue`,基于 ElementPlus 的 `el-table` 二次封装,提供列定义、数据请求、分页、操作列、多选、全屏、密度调整、列设置等能力。
|
||||
`BasicTable` 位于 `src/components/Table/src/Table.vue`,基于 Ant Design Vue 的 `a-table` 二次封装,提供列定义、数据请求、分页、操作列、多选、全屏、密度调整、列设置等能力。
|
||||
:::
|
||||
|
||||
## 组件架构
|
||||
@@ -36,44 +36,98 @@ src/components/Table/
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="tableRef"
|
||||
:actionColumn="actionColumn"
|
||||
@selection-change="onSelectionChange"
|
||||
:row-selection="{ onChange: onSelectionChange }"
|
||||
scroll-x="1200"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<el-button type="primary" @click="handleAdd" v-perm="['sys:position:add']">添加岗位</el-button>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleAdd" v-perm="['sys:position:add']">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新建
|
||||
</a-button>
|
||||
<a-button type="danger" @click="handleDelete()" :disabled="!selectionData.length" v-perm="['sys:position:batchDelete']">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleEdit(record.id)" v-perm="['sys:position:update']">
|
||||
<template #icon><EditOutlined /></template>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="primary" danger @click="handleDelete(record.id)" v-perm="['sys:position:delete']">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</BasicTable>
|
||||
```
|
||||
|
||||
## 列定义
|
||||
|
||||
`columns` 定义表格列,支持 `render` 函数自定义渲染:
|
||||
`columns` 定义表格列,支持 `customRender` 函数自定义渲染:
|
||||
|
||||
```typescript
|
||||
// src/views/system/position/columns.ts
|
||||
|
||||
import { h } from 'vue';
|
||||
import { ElTag } from 'element-plus';
|
||||
import { Tag } from 'ant-design-vue';
|
||||
|
||||
export const columns = [
|
||||
{ type: 'selection' },
|
||||
{ label: 'ID', prop: 'id', fixed: 'left', width: 50 },
|
||||
{ label: '岗位名称', prop: 'name', minWidth: 100 },
|
||||
{
|
||||
label: '岗位状态',
|
||||
prop: 'status',
|
||||
minWidth: 100,
|
||||
render(record) {
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
fixed: 'left',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: '岗位名称',
|
||||
dataIndex: 'name',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '岗位状态',
|
||||
dataIndex: 'status',
|
||||
width: 100,
|
||||
customRender({ record }) {
|
||||
return h(
|
||||
ElTag,
|
||||
{ type: record.row.status == 1 ? 'success' : 'danger' },
|
||||
{ default: () => (record.row.status == 1 ? '正常' : '停用') },
|
||||
Tag,
|
||||
{
|
||||
color: record.status == 1 ? 'success' : 'error',
|
||||
},
|
||||
{
|
||||
default: () => (record.status == 1 ? '正常' : '停用'),
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
{ label: '排序', prop: 'sort', minWidth: 100 },
|
||||
{ label: '创建人', prop: 'createUser', minWidth: 100 },
|
||||
{ label: '创建时间', prop: 'createTime', width: 180 },
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
dataIndex: 'createUser',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
@@ -81,18 +135,14 @@ export const columns = [
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `prop` | string | 字段名 |
|
||||
| `label` | string | 列标题 |
|
||||
| `dataIndex` | string | 字段名 |
|
||||
| `title` | string | 列标题 |
|
||||
| `width` | number | 固定宽度 |
|
||||
| `minWidth` | number | 最小宽度 |
|
||||
| `type` | string | 列类型:`selection` / `index` |
|
||||
| `fixed` | string | 固定列:`left` / `right` |
|
||||
| `render` | function | 自定义渲染函数,接收 `{ row, column, index }` |
|
||||
| `isSlot` | boolean | 是否使用插槽渲染 |
|
||||
| `customRender` | function | 自定义渲染函数,接收 `{ text, record, index, column }` |
|
||||
| `key` | string | 列的唯一标识(操作列必须设置) |
|
||||
| `ellipsis` | boolean | 是否溢出省略 |
|
||||
|
||||
`render` 函数中 `record.row` 是行数据,`record.$index` 是行索引。
|
||||
|
||||
## 数据请求
|
||||
|
||||
`request` 属性传入数据请求函数,组件自动管理分页和 loading:
|
||||
@@ -100,7 +150,7 @@ export const columns = [
|
||||
```typescript
|
||||
const loadDataTable = async (res: any) => {
|
||||
const result = await getPositionList({ ...formParams, ...res });
|
||||
return result; // 返回 { data, count } 结构
|
||||
return result; // 返回 { records, total, size, current, pages } 结构
|
||||
};
|
||||
```
|
||||
|
||||
@@ -108,64 +158,60 @@ const loadDataTable = async (res: any) => {
|
||||
|
||||
## 操作列
|
||||
|
||||
`actionColumn` 使用 `TableAction` 组件渲染行操作按钮,支持权限控制:
|
||||
Ant Design Vue 版本使用 `bodyCell` 插槽渲染行操作按钮,支持权限控制:
|
||||
|
||||
```typescript
|
||||
import { TableAction } from '@/components/Table';
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 200,
|
||||
label: '操作',
|
||||
prop: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
icon: 'Edit',
|
||||
type: 'warning',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
auth: ['sys:position:update'], // 权限节点
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
icon: 'Delete',
|
||||
type: 'danger',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
auth: ['sys:position:delete'], // 权限节点
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
```vue
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleEdit(record.id)" v-perm="['sys:position:update']">
|
||||
<template #icon><EditOutlined /></template>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="primary" danger @click="handleDelete(record.id)" v-perm="['sys:position:delete']">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
```
|
||||
|
||||
`TableAction` 的 `auth` 属性接受权限数组,内部通过 `usePermission` Hook 检查,无权限时自动隐藏按钮。
|
||||
按钮通过 `v-perm` 指令控制权限,无权限时自动隐藏。
|
||||
|
||||
## 多选
|
||||
|
||||
通过 `selection-change` 事件获取选中行:
|
||||
通过 `row-selection` 属性配置多选,使用 `onChange` 回调获取选中行:
|
||||
|
||||
```vue
|
||||
<BasicTable
|
||||
:row-selection="{ onChange: onSelectionChange }"
|
||||
/>
|
||||
```
|
||||
|
||||
```typescript
|
||||
const selectionData = ref([]);
|
||||
|
||||
function onSelectionChange(value) {
|
||||
selectionData.value = value;
|
||||
function onSelectionChange(selectedRowKeys, selectedRows) {
|
||||
selectionData.value = selectedRows;
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
async function handleDelete(record) {
|
||||
async function handleDelete(id) {
|
||||
let ids = [];
|
||||
if (!record) {
|
||||
if (!id) {
|
||||
ids = selectionData.value.map(({ id }) => id);
|
||||
}
|
||||
|
||||
await confirm('确定要删除?');
|
||||
record ? await positionDelete(record.row.id) : await positionBatchDelete(ids);
|
||||
message('删除成功');
|
||||
reloadTable();
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除?',
|
||||
onOk: async () => {
|
||||
id ? await positionDelete(id) : await positionBatchDelete(ids);
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
@@ -184,7 +230,7 @@ function reloadTable(noRefresh = '') {
|
||||
// 操作成功后刷新
|
||||
const handleAdd = async () => {
|
||||
await positionAdd(data);
|
||||
message('操作成功');
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
};
|
||||
```
|
||||
@@ -218,10 +264,15 @@ BasicTable 内置右侧工具栏,支持:
|
||||
const formParams = reactive({ name: '', status: '' });
|
||||
|
||||
const [register, {}] = useForm({
|
||||
labelWidth: 80,
|
||||
layout: 'horizontal',
|
||||
colProps: { span: 6 } as ColProps,
|
||||
submitOnReset: true,
|
||||
rowProps: { gutter: [16, 0] },
|
||||
colProps: {
|
||||
xs: 24,
|
||||
sm: 24,
|
||||
md: 12,
|
||||
lg: 8,
|
||||
xl: 6,
|
||||
},
|
||||
labelCol: { span: 6, offset: 0 },
|
||||
schemas, // querySchemas.ts 中定义
|
||||
});
|
||||
|
||||
@@ -260,9 +311,9 @@ const loadDataTable = async (res: any) => {
|
||||
| `setLoading(loading)` | 设置 loading |
|
||||
|
||||
:::tip 温馨提示
|
||||
BasicTable 内部自动处理分页参数(pageNo/pageSize),调用方只需提供请求函数。操作列的权限控制通过 `TableAction` 的 `auth` 属性实现,与后端 `@permission_required` 对应。`render` 函数使用 Vue 的 `h()` 函数创建虚拟 DOM。
|
||||
BasicTable 内部自动处理分页参数(pageNo/pageSize),调用方只需提供请求函数。操作列通过 `bodyCell` 插槽 + `v-perm` 指令实现权限控制,与后端 `@permission_required` 对应。`customRender` 函数使用 Vue 的 `h()` 函数创建虚拟 DOM。
|
||||
:::
|
||||
|
||||
## 总结
|
||||
|
||||
BasicTable 封装了列定义(含 `render` 函数自定义渲染)、数据请求(自动分页和 loading)、操作列(`TableAction` + 权限控制)、多选、全屏、密度、列设置等表格常用能力。通过 `columns` 配置化定义列,`request` 函数自动管理数据加载,`actionColumn` 配置操作按钮。
|
||||
BasicTable 封装了列定义(含 `customRender` 函数自定义渲染)、数据请求(自动分页和 loading)、操作列(`bodyCell` 插槽 + 权限控制)、多选、全屏、密度、列设置等表格常用能力。通过 `columns` 配置化定义列,`request` 函数自动管理数据加载,`bodyCell` 插槽配置操作按钮。
|
||||
|
||||
@@ -23,18 +23,21 @@ export function getNormalPath(path: string) {
|
||||
return newPath[newPath.length - 1] === '/' ? newPath.slice(0, -1) : newPath;
|
||||
}
|
||||
|
||||
// 确认弹窗封装
|
||||
export function confirm(msg: string, type: 'warning') {
|
||||
return ElMessageBox.confirm(msg, '温馨提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: type,
|
||||
// 确认弹窗封装(使用 Ant Design Vue 的 Modal.confirm)
|
||||
export function confirm(msg: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Modal.confirm({
|
||||
title: '温馨提示',
|
||||
content: msg,
|
||||
onOk: resolve,
|
||||
onCancel: reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 消息提示封装
|
||||
export function message(msg: string, type = 'success') {
|
||||
ElMessage[type](msg);
|
||||
// 消息提示封装(使用 Ant Design Vue 的 message)
|
||||
export function showMsg(msg: string, type = 'success') {
|
||||
message[type](msg);
|
||||
}
|
||||
|
||||
// 文件流下载
|
||||
@@ -235,9 +238,9 @@ export enum PageEnum {
|
||||
```
|
||||
|
||||
:::tip 温馨提示
|
||||
`Storage` 工具类自动将键名转为大写(如 `access-token` → `ACCESS-TOKEN`),存储时包含过期时间,读取时自动检查是否过期。`auth.ts` 中的 `confirm` 和 `message` 函数是对 ElementPlus 的简封装,在业务组件中直接调用即可。
|
||||
`Storage` 工具类自动将键名转为大写(如 `access-token` → `ACCESS-TOKEN`),存储时包含过期时间,读取时自动检查是否过期。`auth.ts` 中的 `confirm` 和 `message` 函数是对 Ant Design Vue 的简封装,在业务组件中直接调用即可。
|
||||
:::
|
||||
|
||||
## 总结
|
||||
|
||||
工具函数库提供了 Storage(本地存储,支持过期)、auth(认证管理、树结构操作、文件下载)、dateUtil(日期格式化)、httpEnum/pageEnum(枚举定义)等通用能力。`confirm` 和 `message` 是 ElementPlus 的简封装,`arrayToTree`/`treeToArray` 处理菜单数据转换。
|
||||
工具函数库提供了 Storage(本地存储,支持过期)、auth(认证管理、树结构操作、文件下载)、dateUtil(日期格式化)、httpEnum/pageEnum(枚举定义)等通用能力。`confirm` 和 `message` 是 Ant Design Vue 的简封装,`arrayToTree`/`treeToArray` 处理菜单数据转换。
|
||||
|
||||
@@ -26,9 +26,9 @@ src/views/
|
||||
| 文件 | 职责 | 说明 |
|
||||
|------|------|------|
|
||||
| `index.vue` | 列表页 | 搜索表单 + 工具栏 + BasicTable + 编辑弹窗引用 |
|
||||
| `edit.vue` | 编辑弹窗 | el-dialog + el-form,懒加载 |
|
||||
| `detail.vue` | 详情弹窗 | el-dialog 展示详情,懒加载 |
|
||||
| `columns.ts` | 表格列定义 | 定义列 prop、label、render 函数等 |
|
||||
| `edit.vue` | 编辑弹窗 | a-modal + a-form,懒加载 |
|
||||
| `detail.vue` | 详情弹窗 | a-modal 展示详情,懒加载 |
|
||||
| `columns.ts` | 表格列定义 | 定列 dataIndex、title、customRender 函数等 |
|
||||
| `querySchemas.ts` | 搜索表单定义 | FormSchema 数组,定义搜索字段 |
|
||||
|
||||
## index.vue 列表页
|
||||
@@ -39,31 +39,52 @@ src/views/
|
||||
<template>
|
||||
<PageWrapper>
|
||||
<!-- 搜索表单卡片 -->
|
||||
<el-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||
<a-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" />
|
||||
</el-card>
|
||||
</a-card>
|
||||
|
||||
<!-- 数据表格卡片 -->
|
||||
<el-card :bordered="false" class="proCard">
|
||||
<a-card :bordered="false" class="proCard">
|
||||
<BasicTable
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="tableRef"
|
||||
:actionColumn="actionColumn"
|
||||
@selection-change="onSelectionChange"
|
||||
:row-selection="{ onChange: onSelectionChange }"
|
||||
scroll-x="1200"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<el-button type="primary" @click="handleAdd" v-perm="['sys:example:add']">
|
||||
添加案例
|
||||
</el-button>
|
||||
<el-button type="danger" @click="handleDelete()" :disabled="!selectionData.length"
|
||||
v-perm="['sys:example:batchDelete']">
|
||||
批量删除
|
||||
</el-button>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleAdd" v-perm="['sys:example:add']">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
添加案例
|
||||
</a-button>
|
||||
<a-button type="danger" @click="handleDelete()" :disabled="!selectionData.length" v-perm="['sys:example:batchDelete']">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
批量删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleEdit(record)" v-perm="['sys:example:update']">
|
||||
<template #icon><EditOutlined /></template>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="primary" @click="handleDetail(record)" v-perm="['sys:example:detail']">
|
||||
<template #icon><EyeOutlined /></template>
|
||||
详情
|
||||
</a-button>
|
||||
<a-button type="primary" danger @click="handleDelete(record)" v-perm="['sys:example:delete']">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</el-card>
|
||||
</a-card>
|
||||
|
||||
<!-- 编辑对话框组件 -->
|
||||
<editDialog v-if="editVisible" :id="currentId" v-model:visible="editVisible" @success="reloadTable('noRefresh')" />
|
||||
@@ -73,13 +94,13 @@ src/views/
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, h, nextTick, defineAsyncComponent } from 'vue';
|
||||
import { reactive, ref, nextTick, defineAsyncComponent } from 'vue';
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons-vue';
|
||||
import { schemas } from './querySchemas';
|
||||
import { useForm } from '@/components/Form/index';
|
||||
import { TableAction } from '@/components/Table';
|
||||
import { getExamplePage, exampleDelete, exampleBatchDelete } from '@/api/tool/example';
|
||||
import { columns } from './columns';
|
||||
import { message, confirm } from '@/utils/auth';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
|
||||
const editDialog = defineAsyncComponent(() => import('./edit.vue'));
|
||||
const detailDialog = defineAsyncComponent(() => import('./detail.vue'));
|
||||
@@ -92,23 +113,6 @@ src/views/
|
||||
|
||||
const formParams = reactive({ name: '', type: '', status: '' });
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 300,
|
||||
label: '操作',
|
||||
prop: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{ label: '编辑', icon: 'Edit', type: 'warning', onClick: handleEdit.bind(null, record), auth: ['sys:example:update'] },
|
||||
{ label: '详情', icon: 'View', type: '', onClick: handleDetail.bind(null, record), auth: ['sys:example:detail'] },
|
||||
{ label: '删除', icon: 'Delete', type: 'danger', onClick: handleDelete.bind(null, record), auth: ['sys:example:delete'] },
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const loadDataTable = async (params: any) => {
|
||||
const result = await getExamplePage({ ...formParams, ...params });
|
||||
return result;
|
||||
@@ -119,14 +123,19 @@ src/views/
|
||||
}
|
||||
|
||||
const [register, {}] = useForm({
|
||||
labelWidth: 80,
|
||||
layout: 'horizontal',
|
||||
colProps: { span: 6 },
|
||||
submitOnReset: true,
|
||||
rowProps: { gutter: [16, 0] },
|
||||
colProps: {
|
||||
xs: 24,
|
||||
sm: 24,
|
||||
md: 12,
|
||||
lg: 8,
|
||||
xl: 6,
|
||||
},
|
||||
labelCol: { span: 6, offset: 0 },
|
||||
schemas,
|
||||
});
|
||||
|
||||
function handleSubmit(values) {
|
||||
function handleSubmit(values: Recordable) {
|
||||
handleReset();
|
||||
for (const key in values) { formParams[key] = values[key]; }
|
||||
reloadTable();
|
||||
@@ -137,22 +146,27 @@ src/views/
|
||||
}
|
||||
|
||||
const handleAdd = async () => { currentId.value = 0; await nextTick(); editVisible.value = true; };
|
||||
const handleEdit = async (record) => { currentId.value = record.row.id; await nextTick(); editVisible.value = true; };
|
||||
const handleDetail = async (record) => { currentId.value = record.row.id; await nextTick(); detailVisible.value = true; };
|
||||
const handleEdit = async (record: Recordable) => { currentId.value = record.id; await nextTick(); editVisible.value = true; };
|
||||
const handleDetail = async (record: Recordable) => { currentId.value = record.id; await nextTick(); detailVisible.value = true; };
|
||||
|
||||
async function handleDelete(record) {
|
||||
async function handleDelete(record: Recordable) {
|
||||
let ids = [];
|
||||
if (!record) {
|
||||
ids = selectionData.value.map(({ id }) => id);
|
||||
if (!ids.length) { message('请选择要删除的数据', 'warning'); return; }
|
||||
if (!ids.length) { message.warning('请选择要删除的数据'); return; }
|
||||
}
|
||||
await confirm('确定要删除选中的案例吗?');
|
||||
record ? await exampleDelete(record.row.id) : await exampleBatchDelete(ids);
|
||||
message('删除成功');
|
||||
reloadTable();
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的案例吗?',
|
||||
onOk: async () => {
|
||||
record ? await exampleDelete(record.id) : await exampleBatchDelete(ids);
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function onSelectionChange(value) { selectionData.value = value; }
|
||||
function onSelectionChange(selectedRowKeys, selectedRows) { selectionData.value = selectedRows; }
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -160,37 +174,72 @@ src/views/
|
||||
|
||||
```typescript
|
||||
import { h } from 'vue';
|
||||
import { ElAvatar, ElTag } from 'element-plus';
|
||||
import { Tag, Image } from 'ant-design-vue';
|
||||
|
||||
export const columns = [
|
||||
{ type: 'selection' },
|
||||
{ label: 'ID', prop: 'id', fixed: 'left', width: 50 },
|
||||
{ label: '案例名称', prop: 'name', minWidth: 120 },
|
||||
{
|
||||
label: '案例图片', prop: 'avatar', minWidth: 120,
|
||||
render(record) {
|
||||
return h(ElAvatar, { size: 48, src: record.row.avatar, shape: 'square', fit: 'fill' });
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
fixed: 'left',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: '案例名称',
|
||||
dataIndex: 'name',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '案例图片',
|
||||
dataIndex: 'avatar',
|
||||
width: 150,
|
||||
customRender({ record }) {
|
||||
return record.avatar
|
||||
? h(Image, { src: record.avatar, width: 48, height: 48, fit: 'cover', preview: true })
|
||||
: h('span', '-');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '案例类型', prop: 'type', minWidth: 100,
|
||||
render(record) {
|
||||
const statusMap = { 1: { text: '类型1', type: 'success' }, 2: { text: '类型2', type: 'danger' } };
|
||||
const status = statusMap[record.row.type];
|
||||
return h(ElTag, { type: status?.type || 'info' }, { default: () => status?.text || record.row.type });
|
||||
title: '案例类型',
|
||||
dataIndex: 'type',
|
||||
width: 100,
|
||||
customRender({ record }) {
|
||||
const statusMap = { 1: { text: '类型1', color: 'success' }, 2: { text: '类型2', color: 'error' }, 3: { text: '类型3', color: 'warning' }, 4: { text: '类型4', color: 'default' } };
|
||||
const status = statusMap[record.type];
|
||||
return h(Tag, { color: status?.color || 'default' }, { default: () => status?.text || record.type });
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '案例状态', prop: 'status', minWidth: 100,
|
||||
render(record) {
|
||||
const statusMap = { 1: { text: '正常', type: 'success' }, 2: { text: '禁用', type: 'danger' } };
|
||||
const status = statusMap[record.row.status];
|
||||
return h(ElTag, { type: status?.type || 'info' }, { default: () => status?.text || record.row.status });
|
||||
title: '案例状态',
|
||||
dataIndex: 'status',
|
||||
width: 100,
|
||||
customRender({ record }) {
|
||||
const statusMap = { 1: { text: '正常', color: 'success' }, 2: { text: '禁用', color: 'error' } };
|
||||
const status = statusMap[record.status];
|
||||
return h(Tag, { color: status?.color || 'default' }, { default: () => status?.text || record.status });
|
||||
},
|
||||
},
|
||||
{ label: '排序', prop: 'sort', minWidth: 100 },
|
||||
{ label: '创建人', prop: 'createUser', minWidth: 100 },
|
||||
{ label: '创建时间', prop: 'createTime', width: 180 },
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
dataIndex: 'createUser',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
@@ -201,23 +250,35 @@ import { FormSchema } from '@/components/Form/index';
|
||||
|
||||
export const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'name', component: 'Input', label: '案例名称',
|
||||
componentProps: { placeholder: '请输入案例名称', clearable: true },
|
||||
name: 'name',
|
||||
component: 'Input',
|
||||
label: '案例名称',
|
||||
componentProps: { placeholder: '请输入案例名称', clearable: true, allowClear: true },
|
||||
},
|
||||
{
|
||||
field: 'type', component: 'Select', label: '案例类型',
|
||||
name: 'type',
|
||||
component: 'Select',
|
||||
label: '案例类型',
|
||||
componentProps: {
|
||||
placeholder: '请输入案例类型', clearable: true,
|
||||
placeholder: '请输入案例类型',
|
||||
clearable: true,
|
||||
allowClear: true,
|
||||
options: [
|
||||
{ label: '类型1', value: 1 },
|
||||
{ label: '类型2', value: 2 },
|
||||
{ label: '类型3', value: 3 },
|
||||
{ label: '类型4', value: 4 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status', component: 'Select', label: '案例状态',
|
||||
name: 'status',
|
||||
component: 'Select',
|
||||
label: '案例状态',
|
||||
componentProps: {
|
||||
placeholder: '请输入案例状态', clearable: true,
|
||||
placeholder: '请输入案例状态',
|
||||
clearable: true,
|
||||
allowClear: true,
|
||||
options: [
|
||||
{ label: '正常', value: 1 },
|
||||
{ label: '禁用', value: 2 },
|
||||
@@ -234,13 +295,13 @@ export const schemas: FormSchema[] = [
|
||||
用户搜索 → handleSubmit → 更新 formParams → reloadTable → 重新请求
|
||||
用户新增 → handleAdd → currentId=0 → editVisible=true → edit.vue 加载 → 表单填写 → 提交
|
||||
用户编辑 → handleEdit → currentId=id → editVisible=true → edit.vue 加载详情 → 修改 → 提交
|
||||
用户删除 → confirm → API → reloadTable
|
||||
用户删除 → Modal.confirm → API → reloadTable
|
||||
```
|
||||
|
||||
:::tip 温馨提示
|
||||
`columns.ts` 和 `querySchemas.ts` 独立为配置文件,便于维护和复用。`columns` 中的 `render` 函数使用 Vue 的 `h()` 创建虚拟 DOM,可实现任意复杂的自定义渲染。`querySchemas` 中的 `options` 硬编码或通过字典 API 动态获取。
|
||||
`columns.ts` 和 `querySchemas.ts` 独立为配置文件,便于维护和复用。`columns` 中的 `customRender` 函数使用 Vue 的 `h()` 创建虚拟 DOM,可实现任意复杂的自定义渲染。`querySchemas` 中的 `options` 硬编码或通过字典 API 动态获取。
|
||||
:::
|
||||
|
||||
## 总结
|
||||
|
||||
页面视图遵循 `index.vue` + `edit.vue` + `detail.vue` + `columns.ts` + `querySchemas.ts` 标准模式。列表页使用 `BasicForm` 搜索 + `BasicTable` 展示数据 + `TableAction` 操作按钮,编辑弹窗使用 `defineAsyncComponent` 懒加载 + `useLockFn` 防重复提交。
|
||||
页面视图遵循 `index.vue` + `edit.vue` + `detail.vue` + `columns.ts` + `querySchemas.ts` 标准模式。列表页使用 `BasicForm` 搜索 + `BasicTable` 展示数据 + `bodyCell` 插槽操作按钮,编辑弹窗使用 `defineAsyncComponent` 懒加载 + `useLockFn` 防重复提交。
|
||||
|
||||
@@ -99,16 +99,15 @@ transformRequestData: (res: AxiosResponse<Result>, options: RequestOptions) => {
|
||||
|
||||
// 业务失败(code === 1):弹出错误提示
|
||||
if (code === ResultEnum.ERROR) {
|
||||
ElMessage.error(msg);
|
||||
message.error(msg);
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
// 登录超时(code === 401):弹出确认框,跳转登录页
|
||||
if (code === ResultEnum.TIMEOUT) {
|
||||
ElMessageBox.confirm('登录身份已失效,请重新登录!', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
showCancelButton: false,
|
||||
type: 'warning',
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '登录身份已失效,请重新登录!',
|
||||
}).then(() => {
|
||||
storage.clear();
|
||||
window.location.href = '/login';
|
||||
@@ -145,22 +144,22 @@ const result = await getExamplePage(params);
|
||||
| code | 说明 | 前端行为 |
|
||||
|------|------|----------|
|
||||
| 0 | 成功 | 返回 `data` |
|
||||
| 1 | 业务失败 | `ElMessage.error(msg)` + `throw` |
|
||||
| 1 | 业务失败 | `message.error(msg)` + `throw` |
|
||||
| 401 | 未登录或 Token 过期 | 弹出确认框 → 跳转登录页 |
|
||||
|
||||
HTTP 状态码始终为 200,业务成功/失败通过 `code` 字段区分。
|
||||
|
||||
## 前端错误展示
|
||||
|
||||
业务错误通过 `ElMessage.error` 弹出提示,无需组件手动处理:
|
||||
业务错误通过 `message.error` 弹出提示,无需组件手动处理:
|
||||
|
||||
```typescript
|
||||
// 组件中直接调用,错误由拦截器处理
|
||||
try {
|
||||
await positionAdd(formData);
|
||||
message('操作成功');
|
||||
message.success('操作成功');
|
||||
} catch (e) {
|
||||
// 拦截器已弹出 ElMessage.error,此处可选处理
|
||||
// 拦截器已弹出 message.error,此处可选处理
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -181,23 +181,23 @@ export function getDictItemByCode(code) {
|
||||
```typescript
|
||||
// src/views/system/param/columns.ts
|
||||
{
|
||||
label: '参数类型',
|
||||
prop: 'type',
|
||||
title: '参数类型',
|
||||
dataIndex: 'type',
|
||||
width: 100,
|
||||
render(record) {
|
||||
return h(ElTag,
|
||||
{ type: record.row.type == 0 ? 'primary' : 'warning' },
|
||||
{ default: () => record.row.type == 0 ? '系统' : '业务' });
|
||||
customRender({ record }) {
|
||||
return h(Tag,
|
||||
{ color: record.type == 0 ? 'blue' : 'warning' },
|
||||
{ default: () => record.type == 0 ? '系统' : '业务' });
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 100,
|
||||
render(record) {
|
||||
return h(ElTag,
|
||||
{ type: record.row.status == 1 ? 'success' : 'danger' },
|
||||
{ default: () => record.row.status == 1 ? '正常' : '禁用' });
|
||||
customRender({ record }) {
|
||||
return h(Tag,
|
||||
{ color: record.status == 1 ? 'success' : 'error' },
|
||||
{ default: () => record.status == 1 ? '正常' : '禁用' });
|
||||
},
|
||||
},
|
||||
```
|
||||
@@ -218,8 +218,8 @@ const options = [
|
||||
// 业务字段为数字
|
||||
formData.type = 0;
|
||||
|
||||
// el-option 绑定时需要转换
|
||||
<el-option :value="Number(item.value)" />
|
||||
// a-select-option 绑定时需要转换
|
||||
<a-select-option :value="Number(item.value)">{{ item.label }}</a-select-option>
|
||||
```
|
||||
|
||||
搜索表单中的 `options` 的 `value` 为字符串 `'0'`/`'1'`,后端筛选字段期望数字。前端 API 传参时自动转换。
|
||||
|
||||
@@ -55,13 +55,13 @@ class ExampleAddView(PermissionRequired, View):
|
||||
|
||||
```vue
|
||||
<!-- src/views/system/position/index.vue -->
|
||||
<el-button type="primary" @click="handleAdd" v-perm="['sys:position:add']">
|
||||
<a-button type="primary" @click="handleAdd" v-perm="['sys:position:add']">
|
||||
添加岗位
|
||||
</el-button>
|
||||
<el-button type="danger" @click="handleDelete()" :disabled="!selectionData.length"
|
||||
</a-button>
|
||||
<a-button type="danger" @click="handleDelete()" :disabled="!selectionData.length"
|
||||
v-perm="['sys:position:batchDelete']">
|
||||
删除
|
||||
</el-button>
|
||||
</a-button>
|
||||
```
|
||||
|
||||
### TableAction auth 属性
|
||||
|
||||
@@ -129,18 +129,19 @@ Service 层的 `get_file_url()` 会自动为文件字段补此前缀。
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-upload
|
||||
<a-upload
|
||||
:action="uploadUrl"
|
||||
:headers="headers"
|
||||
:on-success="handleSuccess"
|
||||
@change="handleChange"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<el-button type="primary">上传文件</el-button>
|
||||
</el-upload>
|
||||
<a-button type="primary">上传文件</a-button>
|
||||
</a-upload>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
const uploadUrl = '/api/upload/uploadFile';
|
||||
@@ -152,7 +153,7 @@ const headers = {
|
||||
function beforeUpload(file) {
|
||||
const isLt10M = file.size / 1024 / 1024 < 10;
|
||||
if (!isLt10M) {
|
||||
ElMessage.error('文件大小不能超过 10MB');
|
||||
message.error('文件大小不能超过 10MB');
|
||||
}
|
||||
return isLt10M;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ Web API 批量生成最多支持 50 个表。CLI 无限制,但建议逐表生
|
||||
**解决**:确保在项目根目录(包含 `manage.py` 和 `generator.py` 的目录)执行:
|
||||
|
||||
```bash
|
||||
cd /data/apps/DjangoAdmin_Django_EleVue
|
||||
cd /data/apps/DjangoAdmin_Django_AntdVue
|
||||
python generator.py django_example
|
||||
```
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ Jinja2 模板文件,分为后端模板(7 个)和前端模板(ui/ 6 个 +
|
||||
## 使用限制
|
||||
|
||||
:::warning 使用前提
|
||||
1. **数据库要求**:代码生成器基于 MySQL 的 `information_schema` 查询表结构,仅支持 MySQL 数据库。
|
||||
1. **数据库要求**:代码生成器基于 MySQL 的 `information_schema` 查询表结构,仅支持 MySQL 数据库。其他数据库(PostgreSQL、Oracle、SQL Server 等)需手动创建模块。
|
||||
2. **表结构要求**:不支持复合主键、外键关联等复杂表结构,建议使用单主键(自增 id)的标准业务表。
|
||||
3. **环境要求**:需确保 Django 环境已初始化、MySQL 和 Redis 服务正常运行。
|
||||
:::
|
||||
|
||||
@@ -162,7 +162,7 @@ public/templates/
|
||||
|
||||
修改 `public/templates/models.py.tpl`:
|
||||
|
||||
```jinja2
|
||||
```jinja
|
||||
{% if f.type == 'CharField' %}
|
||||
{{ f.name }} = models.CharField(
|
||||
max_length={{ f.max_length or 255 }},
|
||||
@@ -177,7 +177,7 @@ public/templates/
|
||||
|
||||
修改 `public/templates/services.py.tpl`,在添加方法中添加自定义校验:
|
||||
|
||||
```jinja2
|
||||
```jinja
|
||||
def add_{{ app_name }}(request):
|
||||
# ... 原有代码 ...
|
||||
# 自定义校验:检查名称是否重复
|
||||
@@ -190,7 +190,7 @@ def add_{{ app_name }}(request):
|
||||
|
||||
修改 `public/templates/ui/columns.ts.tpl`:
|
||||
|
||||
```jinja2
|
||||
```jinja
|
||||
{% for f in list_fields %}
|
||||
{
|
||||
label: '{{ f.label }}',
|
||||
@@ -208,7 +208,7 @@ def add_{{ app_name }}(request):
|
||||
|
||||
在 `public/templates/` 下新建 `test.py.tpl`:
|
||||
|
||||
```jinja2
|
||||
```jinja
|
||||
# tests/test_{{ app_name }}.py
|
||||
from django.test import TestCase
|
||||
from application.{{ app_name }}.models import {{ model_class_name }}
|
||||
@@ -247,7 +247,7 @@ backend_templates = [
|
||||
|
||||
## Jinja2 语法速查
|
||||
|
||||
```jinja2
|
||||
```jinja
|
||||
{# 注释 #}
|
||||
|
||||
{# 变量输出 #}
|
||||
|
||||
@@ -44,7 +44,7 @@ mysqlbinlog --start-datetime="2026-09-05 00:00:00" \
|
||||
|
||||
:::tip binlog 配置
|
||||
确保 MySQL 配置中启用了 binlog:
|
||||
```ini
|
||||
```bash
|
||||
[mysqld]
|
||||
server-id = 1
|
||||
log-bin = mysql-bin
|
||||
@@ -76,10 +76,10 @@ Redis 用于缓存和 JWT token 黑名单,数据可从数据库重建。建议
|
||||
|
||||
```bash
|
||||
# 打包上传目录
|
||||
tar -czf uploads_$(date +%Y%m%d).tar.gz /data/apps/DjangoAdmin_Django_EleVue/public/uploads/
|
||||
tar -czf uploads_$(date +%Y%m%d).tar.gz /data/apps/DjangoAdmin_Django_AntdVue/public/uploads/
|
||||
|
||||
# 使用 rsync 增量同步到远程
|
||||
rsync -avz --delete /data/apps/DjangoAdmin_Django_EleVue/public/uploads/ backup@10.0.2.100:/data/backup/uploads/
|
||||
rsync -avz --delete /data/apps/DjangoAdmin_Django_AntdVue/public/uploads/ backup@10.0.2.100:/data/backup/uploads/
|
||||
```
|
||||
|
||||
## 自动化备份
|
||||
@@ -110,7 +110,7 @@ cp /var/lib/redis/dump.rdb ${BACKUP_DIR}/redis/redis_${DATE}.rdb
|
||||
|
||||
# 上传文件备份
|
||||
tar -czf ${BACKUP_DIR}/uploads/uploads_${DATE}.tar.gz \
|
||||
/data/apps/DjangoAdmin_Django_EleVue/public/uploads/
|
||||
/data/apps/DjangoAdmin_Django_AntdVue/public/uploads/
|
||||
|
||||
# 清理过期备份
|
||||
find ${BACKUP_DIR} -type f -mtime +${RETENTION_DAYS} -delete
|
||||
|
||||
@@ -167,7 +167,7 @@ stages:
|
||||
|
||||
### pytest 配置
|
||||
|
||||
```ini
|
||||
```bash
|
||||
# pytest.ini
|
||||
[pytest]
|
||||
DJANGO_SETTINGS_MODULE = application.settings
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
:::tip 迁移工具链
|
||||
- **结构迁移**:Django 内置迁移系统(`makemigrations` + `migrate`)
|
||||
- **字段注释**:Django 4.1+ 通过 `db_comment` 参数自动添加数据库字段注释
|
||||
- **自定义命令**:`python manage.py add_full_comments` 回填数据库字段注释
|
||||
:::
|
||||
|
||||
## 全新安装初始化
|
||||
@@ -99,7 +98,7 @@ python manage.py migrate --fake example
|
||||
|
||||
## 字段注释管理
|
||||
|
||||
Django 4.1+ 支持通过 `db_comment` 参数为数据库字段添加注释。项目中的模型已自动生成 `db_comment`:
|
||||
Django 4.1+ 支持通过 `db_comment` 参数为数据库字段添加注释。项目中的模型已自动生成 `db_comment`,在执行 `migrate` 时会自动同步到数据库:
|
||||
|
||||
```python
|
||||
class Example(BaseModel):
|
||||
@@ -110,16 +109,6 @@ class Example(BaseModel):
|
||||
)
|
||||
```
|
||||
|
||||
### 回填字段注释
|
||||
|
||||
对于已有数据库,可使用自定义命令回填字段注释:
|
||||
|
||||
```bash
|
||||
python manage.py add_full_comments
|
||||
```
|
||||
|
||||
该命令会遍历所有模型,为缺少注释的字段补充 `db_comment`。
|
||||
|
||||
## 迁移文件结构
|
||||
|
||||
每个应用的迁移文件位于 `application/{app_name}/migrations/` 目录:
|
||||
@@ -150,7 +139,6 @@ application/example/
|
||||
| 回滚指定应用 | `python manage.py migrate <app> zero` |
|
||||
| 查看迁移状态 | `python manage.py showmigrations` |
|
||||
| 查看迁移 SQL | `python manage.py sqlmigrate <app> <migration>` |
|
||||
| 回填字段注释 | `python manage.py add_full_comments` |
|
||||
|
||||
## 常见问题与排错
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ server {
|
||||
gzip_vary on;
|
||||
|
||||
# 前端静态资源
|
||||
root /data/apps/DjangoAdmin_Django_EleVue/ui/dist;
|
||||
root /data/apps/DjangoAdmin_Django_AntdVue/ui/dist;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
@@ -70,14 +70,14 @@ server {
|
||||
|
||||
# Django 静态文件
|
||||
location /static/ {
|
||||
alias /data/apps/DjangoAdmin_Django_EleVue/public/static/;
|
||||
alias /data/apps/DjangoAdmin_Django_AntdVue/public/static/;
|
||||
expires 30d;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# 上传文件访问
|
||||
location /uploads/ {
|
||||
alias /data/apps/DjangoAdmin_Django_EleVue/public/uploads/;
|
||||
alias /data/apps/DjangoAdmin_Django_AntdVue/public/uploads/;
|
||||
expires 7d;
|
||||
access_log off;
|
||||
limit_except GET { deny all; }
|
||||
@@ -105,8 +105,8 @@ server {
|
||||
cd ui && pnpm build
|
||||
|
||||
# 2. 拷贝构建产物
|
||||
mkdir -p /data/apps/DjangoAdmin_Django_EleVue/ui/dist
|
||||
cp -r ui/dist /data/apps/DjangoAdmin_Django_EleVue/ui/
|
||||
mkdir -p /data/apps/DjangoAdmin_Django_AntdVue/ui/dist
|
||||
cp -r ui/dist /data/apps/DjangoAdmin_Django_AntdVue/ui/
|
||||
|
||||
# 3. 收集 Django 静态文件
|
||||
python manage.py collectstatic
|
||||
|
||||
@@ -239,7 +239,7 @@ class MasterSlaveRouter:
|
||||
|
||||
## Supervisor 多实例配置
|
||||
|
||||
```ini
|
||||
```bash
|
||||
; /etc/supervisor/conf.d/djangoadmin.conf
|
||||
[program:djangoadmin-1]
|
||||
command=/opt/venv/bin/gunicorn application.wsgi:application -c gunicorn.conf.py -b 127.0.0.1:8001
|
||||
|
||||
@@ -12,7 +12,7 @@ Supervisor 是一个 Python 编写的进程管理工具,用于在 Linux 服务
|
||||
|
||||
## 配置文件示例
|
||||
|
||||
```ini
|
||||
```bash
|
||||
; Supervisor 进程管理配置
|
||||
; 将 command / directory 替换为实际路径后复制到 /etc/supervisor/conf.d/
|
||||
|
||||
@@ -20,7 +20,7 @@ Supervisor 是一个 Python 编写的进程管理工具,用于在 Linux 服务
|
||||
; 启动命令:使用 gunicorn 运行 Django WSGI 应用
|
||||
command=/path/to/venv/bin/gunicorn application.wsgi:application --bind 127.0.0.1:8000 --workers 4 --timeout 120
|
||||
; 项目根目录
|
||||
directory=/data/apps/DjangoAdmin_Django_EleVue
|
||||
directory=/data/apps/DjangoAdmin_Django_AntdVue
|
||||
; 自动启动
|
||||
autostart=true
|
||||
; 异常退出后自动重启
|
||||
@@ -134,7 +134,7 @@ gunicorn application.wsgi:application \
|
||||
|
||||
Supervisor 内置日志轮转功能,通过以下配置控制:
|
||||
|
||||
```ini
|
||||
```bash
|
||||
; 单个日志文件最大 50MB
|
||||
stdout_logfile_maxbytes=50MB
|
||||
; 保留 10 个历史日志文件
|
||||
@@ -153,16 +153,16 @@ stdout_logfile_backups=10
|
||||
|
||||
如需同时管理多个进程(如应用 + Celery Worker):
|
||||
|
||||
```ini
|
||||
```bash
|
||||
[program:djangoadmin]
|
||||
command=/path/to/venv/bin/gunicorn application.wsgi:application --bind 127.0.0.1:8000 --workers 4
|
||||
directory=/data/apps/DjangoAdmin_Django_EleVue
|
||||
directory=/data/apps/DjangoAdmin_Django_AntdVue
|
||||
autostart=true
|
||||
autorestart=true
|
||||
|
||||
[program:djangoadmin-celery]
|
||||
command=/path/to/venv/bin/celery -A application worker -l info
|
||||
directory=/data/apps/DjangoAdmin_Django_EleVue
|
||||
directory=/data/apps/DjangoAdmin_Django_AntdVue
|
||||
autostart=true
|
||||
autorestart=true
|
||||
numprocs=2
|
||||
|
||||
@@ -170,8 +170,8 @@ pip show Pillow
|
||||
|
||||
```bash
|
||||
# 1. 检查上传目录权限
|
||||
ls -la /data/apps/DjangoAdmin_Django_EleVue/public/uploads/
|
||||
chmod 755 /data/apps/DjangoAdmin_Django_EleVue/public/uploads/
|
||||
ls -la /data/apps/DjangoAdmin_Django_AntdVue/public/uploads/
|
||||
chmod 755 /data/apps/DjangoAdmin_Django_AntdVue/public/uploads/
|
||||
|
||||
# 2. 检查磁盘空间
|
||||
df -h
|
||||
|
||||
@@ -21,12 +21,12 @@ mysqldump -h 127.0.0.1 -u root -p --single-transaction \
|
||||
djangoadmin.django.elevue | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz
|
||||
|
||||
# 2. 上传文件备份
|
||||
tar -czf uploads_$(date +%Y%m%d).tar.gz /data/apps/DjangoAdmin_Django_EleVue/public/uploads/
|
||||
tar -czf uploads_$(date +%Y%m%d).tar.gz /data/apps/DjangoAdmin_Django_AntdVue/public/uploads/
|
||||
|
||||
# 3. 应用目录备份(含 .env 和配置)
|
||||
tar -czf app_$(date +%Y%m%d).tar.gz \
|
||||
--exclude='uploads' --exclude='__pycache__' --exclude='.git' \
|
||||
/data/apps/DjangoAdmin_Django_EleVue/
|
||||
/data/apps/DjangoAdmin_Django_AntdVue/
|
||||
```
|
||||
|
||||
### 查看变更日志
|
||||
@@ -54,7 +54,7 @@ git diff --stat HEAD~10
|
||||
### 步骤一:拉取代码
|
||||
|
||||
```bash
|
||||
cd /data/apps/DjangoAdmin_Django_EleVue
|
||||
cd /data/apps/DjangoAdmin_Django_AntdVue
|
||||
|
||||
# 拉取最新代码
|
||||
git fetch origin
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
## 概述
|
||||
|
||||
前端基于 Vue3 + ElementPlus + 自定义组件库构建。页面统一使用 `PageWrapper` + `BasicForm` + `BasicTable` + `TableAction` 四件套,分页由 `BasicTable` 内置处理,无需单独引入分页组件。
|
||||
前端基于 Vue3 + Ant Design Vue + 自定义组件库构建。页面统一使用 `PageWrapper` + `BasicForm` + `BasicTable` + `bodyCell` 插槽四件套,分页由 `BasicTable` 内置处理,无需单独引入分页组件。
|
||||
|
||||
:::tip 技术栈
|
||||
- Vue3(Composition API + `<script setup>`)
|
||||
- ElementPlus 2.x
|
||||
- Ant Design Vue 3.x
|
||||
- 自定义组件(BasicTable / BasicForm / TableAction / PageWrapper)
|
||||
- Pinia 状态管理
|
||||
- Vue Router 路由管理
|
||||
@@ -56,7 +56,7 @@ ui/src/
|
||||
├── utils/ # 工具函数
|
||||
├── hooks/ # 组合式函数
|
||||
├── styles/ # 全局样式
|
||||
├── plugins/ # 插件注册(ElementPlus、自定义组件、指令)
|
||||
├── plugins/ # 插件注册(Ant Design Vue、自定义组件、指令)
|
||||
└── main.ts # 入口文件
|
||||
```
|
||||
|
||||
@@ -79,37 +79,48 @@ ui/src/
|
||||
<template>
|
||||
<PageWrapper>
|
||||
<!-- 搜索表单 -->
|
||||
<el-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||
<a-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" />
|
||||
</el-card>
|
||||
</a-card>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card :bordered="false" class="proCard">
|
||||
<a-card :bordered="false" class="proCard">
|
||||
<BasicTable
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="tableRef"
|
||||
:actionColumn="actionColumn"
|
||||
@selection-change="onSelectionChange"
|
||||
:row-selection="{ onChange: onSelectionChange }"
|
||||
scroll-x="1200"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<el-button type="primary" @click="handleAdd" v-perm="['sys:param:add']">
|
||||
<el-icon><PlusOutlined /></el-icon>
|
||||
添加参数
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
@click="handleDelete()"
|
||||
:disabled="!selectionData.length"
|
||||
v-perm="['sys:param:batchDelete']"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
删除
|
||||
</el-button>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleAdd" v-perm="['sys:param:add']">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
添加参数
|
||||
</a-button>
|
||||
<a-button type="danger" @click="handleDelete()" :disabled="!selectionData.length" v-perm="['sys:param:batchDelete']">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleEdit(record.id)" v-perm="['sys:param:update']">
|
||||
<template #icon><EditOutlined /></template>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="primary" danger @click="handleDelete(record.id)" v-perm="['sys:param:delete']">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</el-card>
|
||||
</a-card>
|
||||
|
||||
<!-- 编辑弹窗(懒加载) -->
|
||||
<editDialog
|
||||
@@ -133,37 +144,63 @@ ui/src/
|
||||
```typescript
|
||||
// views/data/param/columns.ts
|
||||
import { h } from 'vue';
|
||||
import { ElTag } from 'element-plus';
|
||||
import { Tag } from 'ant-design-vue';
|
||||
|
||||
export const columns = [
|
||||
{ type: 'selection' }, // 多选列
|
||||
{ label: 'ID', prop: 'id', width: 50 }, // 固定列
|
||||
{ label: '参数名称', prop: 'name', minWidth: 200 },
|
||||
{ label: '参数编码', prop: 'code', minWidth: 250 },
|
||||
{ label: '参数值', prop: 'value', minWidth: 200 },
|
||||
{
|
||||
label: '参数类型',
|
||||
prop: 'type',
|
||||
minWidth: 100,
|
||||
render(record) {
|
||||
return h(ElTag,
|
||||
{ type: record.row.type == 0 ? 'primary' : 'warning' },
|
||||
{ default: () => (record.row.type == 0 ? '系统' : '业务') }
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: '参数名称',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '参数编码',
|
||||
dataIndex: 'code',
|
||||
width: 250,
|
||||
},
|
||||
{
|
||||
title: '参数值',
|
||||
dataIndex: 'value',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '参数类型',
|
||||
dataIndex: 'type',
|
||||
width: 100,
|
||||
customRender({ record }) {
|
||||
return h(Tag,
|
||||
{ color: record.type == 0 ? 'blue' : 'warning' },
|
||||
{ default: () => (record.type == 0 ? '系统' : '业务') }
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
minWidth: 100,
|
||||
render(record) {
|
||||
return h(ElTag,
|
||||
{ type: record.row.status == 1 ? 'success' : 'danger' },
|
||||
{ default: () => (record.row.status == 1 ? '正常' : '禁用') }
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 100,
|
||||
customRender({ record }) {
|
||||
return h(Tag,
|
||||
{ color: record.status == 1 ? 'success' : 'error' },
|
||||
{ default: () => (record.status == 1 ? '正常' : '禁用') }
|
||||
);
|
||||
},
|
||||
},
|
||||
{ label: '创建时间', prop: 'createTime', width: 180 },
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
@@ -171,10 +208,10 @@ export const columns = [
|
||||
|
||||
```typescript
|
||||
// 状态标签
|
||||
render(record) {
|
||||
return h(ElTag,
|
||||
{ type: record.row.status == 1 ? 'success' : 'danger' },
|
||||
{ default: () => record.row.statusText } // 使用后端 serialize_maps 返回的 xxxText
|
||||
customRender({ record }) {
|
||||
return h(Tag,
|
||||
{ color: record.status == 1 ? 'success' : 'error' },
|
||||
{ default: () => record.statusText } // 使用后端 serialize_maps 返回的 xxxText
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -91,13 +91,13 @@ class ExamplePageView(PermissionRequired, View):
|
||||
|
||||
这些模块不仅可以直接使用,也是学习本框架开发模式的最佳参考。
|
||||
|
||||
## Vue3 + ElementPlus 前端
|
||||
## Vue3 + Ant Design Vue 前端
|
||||
|
||||
前端采用主流技术栈,开发体验优秀:
|
||||
|
||||
- **Vue3 Composition API**:更灵活的组件逻辑组织
|
||||
- **TypeScript**:类型安全,减少运行时错误
|
||||
- **ElementPlus**:成熟的企业级 UI 组件库
|
||||
- **Ant Design Vue**:成熟的企业级 UI 组件库
|
||||
- **Pinia**:轻量级状态管理,替代 Vuex
|
||||
- **Vite**:极速的开发服务器和构建工具
|
||||
- **动态路由**:菜单和路由由后端驱动,前端无需硬编码
|
||||
@@ -143,7 +143,7 @@ cd ui && pnpm install && pnpm dev
|
||||
项目持续迭代,紧跟技术发展:
|
||||
|
||||
- Django 版本持续升级至 6.0.3
|
||||
- 前端从 Vue 2 升级到 Vue3 + ElementPlus
|
||||
- 前端从 Vue 2 升级到 Vue3 + Ant Design Vue
|
||||
- 代码生成器功能不断增强
|
||||
- 安全机制持续完善
|
||||
|
||||
|
||||
@@ -779,10 +779,10 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
|
||||
},
|
||||
|
||||
// ================================================================
|
||||
// 第 9 章:其他
|
||||
// 第 9 章:附录
|
||||
// ================================================================
|
||||
{
|
||||
text: "📄 9. 其他",
|
||||
text: "📄 附录",
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: "9.1 常见问题汇总", link: "/zh/faq.html" },
|
||||
|
||||
Generated
-2304
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
||||
allowBuilds:
|
||||
'@parcel/watcher': set this to true or false
|
||||
esbuild: set this to true or false
|
||||
vue-demi: set this to true or false
|
||||
@@ -18,9 +18,9 @@
|
||||
| 2.7.0 | 2025-12-08 | 接口限流 + IP 地理位置 + 安全防护 |
|
||||
| 2.8.0 | 2026-03-22 | Docker 容器化 + Nginx + Supervisor |
|
||||
| 2.9.0 | 2026-06-15 | 文档体系 + 缓存优化 + 性能提升 |
|
||||
| **3.0.0** | **2026-09-01** | **Django 6.0 + Python 3.12 + 架构优化** |
|
||||
| **3.0.0** | **2026-09-10** | **Django 6.0 + Python 3.12 + 架构优化** |
|
||||
|
||||
## [3.0.0] - 2026-09-01
|
||||
## [3.0.0] - 2026-09-10
|
||||
|
||||
**Django 6.0 升级,技术栈全面焕新。**
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ REDIS_DB=0
|
||||
DJANGO_FILE_URL=http://file.django.elevue
|
||||
```
|
||||
|
||||
:::danger
|
||||
:::danger 重要提示
|
||||
`DATABASE_NAME` 需要提前在 MySQL 中创建对应的数据库:
|
||||
```sql
|
||||
CREATE DATABASE `djangoadmin.django.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
## 软件说明
|
||||
|
||||
基于 Django、Django ORM、Vue3、TypeScript、Vite、ElementPlus、MySQL 等技术栈构建的企业级前后端分离后台管理系统。
|
||||
基于 Django、Django ORM、Vue3、TypeScript、Vite、ElementPlus 等技术栈构建的企业级前后端分离后台管理系统,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库。
|
||||
|
||||
后端采用 Python 语言,基于 Django Web 框架、Django ORM 数据库操作、Django Form 数据校验等主流技术栈;前端基于 Vue3、TypeScript、Vite 构建,采用 ElementPlus 框架,遵循 Vue 前后端分离主流设计思想。
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
+ **分层架构**:Model(数据)→ Form(验证)→ Service(业务)→ View(视图)→ URL(路由),职责清晰
|
||||
+ **基类复用**:BaseModel 提供 6 个公共字段(id、创建人、时间、软删除),子类只需声明业务字段
|
||||
+ **模块可插拔**:业务模块位于 `application/`,按 Django App 机制注册,增删不影响核心框架
|
||||
+ **多数据库支持**:通过 `DATABASE_ENGINE` 环境变量一键切换 MySQL / PostgreSQL / SQLite / Oracle
|
||||
+ **多数据库支持**:通过 `DATABASE_ENGINE` 环境变量一键切换 MySQL / PostgreSQL / SQLite / Oracle / SQL Server 等数据库
|
||||
+ **中间件可扩展**:CORS、认证、权限、日志通过 Django 中间件链式处理,按需注册
|
||||
+ **RBAC 权限**:后端 PermissionRequired + 前端 v-perm 指令双重校验,精确到按钮级
|
||||
+ **代码生成器**:根据数据库表结构一键生成前后端 CRUD 代码,含列表、表单、API 全套
|
||||
@@ -42,9 +42,13 @@
|
||||
+ **通知公告**:主要对通知、公告等站内数据进行管理
|
||||
+ **行政区划**:针对全国行政区划分数据进行管理和维护
|
||||
+ **文件模板**:主要对系统中使用的文件模板进行上传和管理
|
||||
+ **文件上传**:提供统一的文件上传服务,支持图片、文档等多类型文件管理,自动重命名、按日期分目录存储
|
||||
+ **友情链接**:用于管理外部链接和合作伙伴链接数据,支持分类管理和排序
|
||||
+ **文章管理**:用于发布和管理文章内容,支持富文本编辑、分类管理和状态管理
|
||||
+ **任务调度**:主要用于对定时任务进行定义和统一调度管理
|
||||
+ **代码生成**:根据数据库表结构一键生成前后端 CRUD 代码,含列表、表单、API 全套
|
||||
+ **分类管理**:用于文章分类、商品分类等树形分类数据的管理
|
||||
+ **示例模块**:提供代码生成器的示例参考,展示标准 CRUD 模块的实现方式
|
||||
|
||||
## 软件出处
|
||||
|
||||
@@ -59,82 +63,84 @@
|
||||
|
||||
| 版本名称 | 说明 | 地址 |
|
||||
|:---------|:-----|:-----|
|
||||
| Django+Layui混编版 | 基于 Django、Jinja2、Layui、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_Layui) |
|
||||
| Django+EleVue前后端分离版 | 基于 Django、Django ORM、Vue3、ElementPlus、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_EleVue) |
|
||||
| Django+AntdVue前后端分离版 | 基于 Django、Django ORM、Vue3、AntDesign、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_AntdVue) |
|
||||
| Django+NaiveVue前后端分离版 | 基于 Django、Django ORM、Vue3、NaiveUI、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_NaiveVue) |
|
||||
| Flask+Layui混编版 | 基于 Flask、Jinja2、Layui、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_Layui) |
|
||||
| Flask+EleVue前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_EleVue) |
|
||||
| Flask+AntdVue前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_AntdVue) |
|
||||
| Flask+NaiveVue前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_NaiveVue) |
|
||||
| FastAPI+Layui混编版 | 基于 FastAPI、Jinja2、Layui、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_Layui) |
|
||||
| FastAPI+EleVue前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_EleVue) |
|
||||
| FastAPI+AntdVue前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_AntdVue) |
|
||||
| FastAPI+NaiveVue前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_NaiveVue) |
|
||||
| Tornado+Layui混编版 | 基于 Tornado、Jinja2、Layui、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_Layui) |
|
||||
| Tornado+EleVue前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_EleVue) |
|
||||
| Tornado+AntdVue前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_AntdVue) |
|
||||
| Tornado+NaiveVue前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_NaiveVue) |
|
||||
| Django+Layui混编版 | 基于 Django、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_Layui) |
|
||||
| Django+ElementPlus前后端分离版 | 基于 Django、Django ORM、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_EleVue) |
|
||||
| Django+AntDesign前后端分离版 | 基于 Django、Django ORM、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_AntdVue) |
|
||||
| Django+NaiveUI前后端分离版 | 基于 Django、Django ORM、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_NaiveVue) |
|
||||
| Flask+Layui混编版 | 基于 Flask、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_Layui) |
|
||||
| Flask+ElementPlus前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_EleVue) |
|
||||
| Flask+AntDesign前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_AntdVue) |
|
||||
| Flask+NaiveUI前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_NaiveVue) |
|
||||
| FastAPI+Layui混编版 | 基于 FastAPI、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_Layui) |
|
||||
| FastAPI+ElementPlus前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_EleVue) |
|
||||
| FastAPI+AntDesign前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_AntdVue) |
|
||||
| FastAPI+NaiveUI前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_NaiveVue) |
|
||||
| Tornado+Layui混编版 | 基于 Tornado、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_Layui) |
|
||||
| Tornado+ElementPlus前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_EleVue) |
|
||||
| Tornado+AntDesign前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_AntdVue) |
|
||||
| Tornado+NaiveUI前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_NaiveVue) |
|
||||
|
||||
## 模块展示
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="/demo/1.png"/></td>
|
||||
<td><img src="/demo/2.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/3.png"/></td>
|
||||
<td><img src="/demo/4.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/5.png"/></td>
|
||||
<td><img src="/demo/6.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/7.png"/></td>
|
||||
<td><img src="/demo/8.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/9.png"/></td>
|
||||
<td><img src="/demo/10.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/11.png"/></td>
|
||||
<td><img src="/demo/12.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/13.png"/></td>
|
||||
<td><img src="/demo/14.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/15.png"/></td>
|
||||
<td><img src="/demo/16.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/17.png"/></td>
|
||||
<td><img src="/demo/18.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/19.png"/></td>
|
||||
<td><img src="/demo/20.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/21.png"/></td>
|
||||
<td><img src="/demo/22.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/23.png"/></td>
|
||||
<td><img src="/demo/24.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/25.png"/></td>
|
||||
<td><img src="/demo/26.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/27.png"/></td>
|
||||
<td><img src="/demo/28.png"/></td>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><img src="/demo/1.png"/></td>
|
||||
<td><img src="/demo/2.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/3.png"/></td>
|
||||
<td><img src="/demo/4.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/5.png"/></td>
|
||||
<td><img src="/demo/6.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/7.png"/></td>
|
||||
<td><img src="/demo/8.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/9.png"/></td>
|
||||
<td><img src="/demo/10.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/11.png"/></td>
|
||||
<td><img src="/demo/12.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/13.png"/></td>
|
||||
<td><img src="/demo/14.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/15.png"/></td>
|
||||
<td><img src="/demo/16.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/17.png"/></td>
|
||||
<td><img src="/demo/18.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/19.png"/></td>
|
||||
<td><img src="/demo/20.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/21.png"/></td>
|
||||
<td><img src="/demo/22.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/23.png"/></td>
|
||||
<td><img src="/demo/24.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/25.png"/></td>
|
||||
<td><img src="/demo/26.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/27.png"/></td>
|
||||
<td><img src="/demo/28.png"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## 开源协议
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
## 整体结构
|
||||
|
||||
```
|
||||
DjangoAdmin_Django_EleVue/
|
||||
├── application/ # Django 应用目录(所有业务模块)
|
||||
│ ├── example/ # 示例模块(标准 CRUD 模板,可作为开发参考)
|
||||
│ │ ├── models.py # 数据模型
|
||||
|
||||
@@ -204,14 +204,7 @@ python manage.py migrate
|
||||
`makemigrations` 会根据 `models.py` 中的模型定义自动生成迁移文件,`migrate` 会执行迁移文件创建或修改数据表结构。更多数据库相关内容请参见 [数据库初始化](/zh/volume1/database) 章节。
|
||||
:::
|
||||
|
||||
### 第三步:回填数据库字段注释(可选)
|
||||
|
||||
```bash
|
||||
# 为数据库表和字段添加中文注释
|
||||
python manage.py add_full_comments
|
||||
```
|
||||
|
||||
### 第四步:导入初始数据
|
||||
### 第三步:导入初始数据
|
||||
|
||||
数据库初始数据(管理员账号、默认角色、菜单权限等)通过 SQL 脚本导入。脚本位于项目对应目录中:
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ DATABASE_PREFIX="django_" # 数据表前缀
|
||||
| MySQL | `django.db.backends.mysql` | 3306 | mysqlclient |
|
||||
| PostgreSQL | `django.db.backends.postgresql` | 5432 | psycopg2-binary |
|
||||
| SQLite | `django.db.backends.sqlite3` | - | 内置 |
|
||||
| Oracle | `django.db.backends.oracle` | 1521 | cx-Oracle |
|
||||
| Oracle | `django.db.backends.oracle` | 1521 | oracledb |
|
||||
| SQL Server | `mssql` | 1433 | mssql-django |
|
||||
|
||||
切换数据库引擎后,需安装对应的 Python 驱动包。例如切换到 PostgreSQL:
|
||||
@@ -122,20 +122,7 @@ mysql -uroot -p djangoadmin.django.elevue < document/mysql/djangoadmin.django.el
|
||||
| 系统配置 | 系统默认配置项 |
|
||||
:::
|
||||
|
||||
## 第五步:回填数据库字段注释(可选)
|
||||
|
||||
项目提供了自定义管理命令,用于为数据库表和字段添加中文注释:
|
||||
|
||||
```bash
|
||||
# 为所有数据表添加字段注释
|
||||
python manage.py add_full_comments
|
||||
```
|
||||
|
||||
:::tip 温馨提示
|
||||
此命令会读取 Django 模型中的 `verbose_name` 和 `help_text` 属性,将其写入数据库的字段注释中,便于使用 Navicat 等工具查看表结构时理解每个字段的含义。
|
||||
:::
|
||||
|
||||
## 第六步:验证
|
||||
## 第五步:验证
|
||||
|
||||
启动后端服务,验证数据库连接是否正常:
|
||||
|
||||
@@ -271,7 +258,6 @@ cp db_backup.sqlite3 db.sqlite3
|
||||
2. 配置 .env:设置 DATABASE_ENGINE、DATABASE_NAME 等连接信息
|
||||
3. 执行迁移:python manage.py makemigrations && python manage.py migrate
|
||||
4. 导入初始数据:mysql ... < document/mysql/djangoadmin.django.elevue.sql
|
||||
5. 回填注释(可选):python manage.py add_full_comments
|
||||
```
|
||||
|
||||
后续的模型变更通过 Django 迁移系统管理(`makemigrations` + `migrate`),确保数据库结构与代码保持同步。如需使用代码生成器创建新模块,生成器会自动处理数据库迁移。
|
||||
|
||||
@@ -146,7 +146,7 @@ DJANGO_NAME=Django+EleVue旗舰版
|
||||
DJANGO_VERSION=v3.0.0
|
||||
DJANGO_DEBUG=True
|
||||
DJANGO_DEMO=False # 代码默认True(演示模式),开发环境设为False以启用写操作
|
||||
DJANGO_UPLOAD_DIR="E:\DjangoAdmin_V3\DjangoAdmin_Django_EleVue\public\uploads"
|
||||
DJANGO_UPLOAD_DIR="E:\DjangoAdmin_Django_EleVue\public\uploads"
|
||||
DJANGO_FILE_URL='http://file.django.elevue'
|
||||
|
||||
# ============================================================
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
本章节介绍在本地搭建项目所需的基础环境和软件安装。项目采用 `Python` + `Django` 后端、`Vue3` + `ElementPlus` 前端的分离架构,本地开发需准备以下软件。
|
||||
|
||||
安装软件:`Python(3.12+)`、`MySQL(8.0+)`、`Redis(6.0+)`、`Node.js(18+)`、`pnpm` 等。
|
||||
安装软件:`Python(3.12+)`、`MySQL(8.0+)`(或其他支持的数据库)、`Redis(6.0+)`、`Node.js(18+)`、`pnpm` 等。
|
||||
|
||||
:::tip 温馨提示
|
||||
1. 部分软件安装后需要设置本地环境变量,便于在终端中使用命令。
|
||||
|
||||
@@ -378,7 +378,7 @@ python manage.py shell
|
||||
|
||||
```bash
|
||||
# .env 中的上传配置
|
||||
DJANGO_UPLOAD_DIR="E:\DjangoAdmin_V3\DjangoAdmin_Django_EleVue\public\uploads"
|
||||
DJANGO_UPLOAD_DIR="E:\DjangoAdmin_Django_EleVue\public\uploads"
|
||||
DJANGO_FILE_URL='http://file.django.elevue'
|
||||
```
|
||||
|
||||
|
||||
@@ -16,15 +16,32 @@ from django import forms
|
||||
from application.example import models
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 案例表单验证类
|
||||
# =============================================================
|
||||
|
||||
class ExampleForm(forms.ModelForm):
|
||||
"""
|
||||
案例表单验证类
|
||||
|
||||
用于创建和更新案例信息时的表单验证。
|
||||
验证字段包括:name, avatar, type, status, sort
|
||||
包含案例名称, 案例图片, 案例类型等属性
|
||||
|
||||
验证字段包括:
|
||||
- name: 案例名称
|
||||
- avatar: 案例图片
|
||||
- type: 案例类型
|
||||
- status: 案例状态(1-正常, 2-禁用)
|
||||
- sort: 排序
|
||||
"""
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 案例名称
|
||||
# ----------------------------------------------------------
|
||||
# 字段说明: 案例的名称
|
||||
# 字段类型: 字符串(CharField)
|
||||
# 验证规则: 必填(required=True),最大长度100字符
|
||||
# 业务说明: 案例的核心标识字段,用于列表展示和搜索
|
||||
name = forms.CharField(
|
||||
required=True,
|
||||
max_length=100,
|
||||
@@ -34,7 +51,13 @@ class ExampleForm(forms.ModelForm):
|
||||
}
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 案例图片
|
||||
# ----------------------------------------------------------
|
||||
# 字段说明: 案例的图片
|
||||
# 字段类型: 字符串(CharField)
|
||||
# 验证规则: 可选(required=False),最大长度255字符
|
||||
# 业务说明: 用于案例列表展示和详情页的图片
|
||||
avatar = forms.CharField(
|
||||
required=False,
|
||||
max_length=255,
|
||||
@@ -43,7 +66,13 @@ class ExampleForm(forms.ModelForm):
|
||||
}
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 案例类型
|
||||
# ----------------------------------------------------------
|
||||
# 字段说明: 案例的分类类型
|
||||
# 字段类型: 整数(IntegerField)
|
||||
# 验证规则: 必填(required=True),取值范围1-4
|
||||
# 业务说明: 对案例进行分类管理,支持按类型筛选
|
||||
type = forms.IntegerField(
|
||||
required=True,
|
||||
min_value=1,
|
||||
@@ -55,7 +84,13 @@ class ExampleForm(forms.ModelForm):
|
||||
}
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 案例状态
|
||||
# ----------------------------------------------------------
|
||||
# 字段说明: 案例的显示状态
|
||||
# 字段类型: 整数(IntegerField)
|
||||
# 验证规则: 必填(required=True),取值范围1-2
|
||||
# 业务说明: 1-正常, 2-禁用,控制案例是否在前端展示
|
||||
status = forms.IntegerField(
|
||||
required=True,
|
||||
min_value=1,
|
||||
@@ -67,7 +102,14 @@ class ExampleForm(forms.ModelForm):
|
||||
}
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 排序
|
||||
# ----------------------------------------------------------
|
||||
# 字段说明: 案例的排序权重
|
||||
# 字段类型: 整数(IntegerField)
|
||||
# 验证规则: 必填(required=True)
|
||||
# 排序规则: 数值越小越靠前
|
||||
# 业务说明: 手动控制案例在列表中的显示顺序
|
||||
sort = forms.IntegerField(
|
||||
required=True,
|
||||
error_messages={
|
||||
@@ -75,8 +117,23 @@ class ExampleForm(forms.ModelForm):
|
||||
}
|
||||
)
|
||||
|
||||
# =============================================================
|
||||
# 表单元数据配置
|
||||
# =============================================================
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
表单元数据配置类
|
||||
|
||||
定义表单绑定的模型和需要验证的字段。
|
||||
"""
|
||||
|
||||
# 绑定模型
|
||||
# 指定该表单对应的数据模型为 Example(案例)
|
||||
model = models.Example
|
||||
|
||||
# 指定需要验证的字段
|
||||
# 只验证以下字段,其他字段不进行表单验证
|
||||
fields = [
|
||||
'name',
|
||||
'avatar',
|
||||
|
||||
@@ -17,6 +17,10 @@ from application.models import BaseModel
|
||||
from utils.common import get_table_name
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 案例模型类
|
||||
# =============================================================
|
||||
|
||||
class Example(BaseModel):
|
||||
"""
|
||||
案例模型类
|
||||
@@ -29,29 +33,53 @@ class Example(BaseModel):
|
||||
- create_time: 创建时间
|
||||
- update_user: 更新人
|
||||
- update_time: 更新时间
|
||||
|
||||
主要业务字段:
|
||||
- name: 案例名称
|
||||
- avatar: 案例图片
|
||||
- type: 案例类型
|
||||
- status: 案例状态(1-正常, 2-禁用)
|
||||
- sort: 排序权重(数值越小越靠前)
|
||||
"""
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 案例名称
|
||||
# ----------------------------------------------------------
|
||||
# 字段说明: 案例的名称
|
||||
# 字段类型: 字符串(CharField)
|
||||
# 约束: 非空(null=False),最大长度100字符
|
||||
# 索引: 添加了数据库索引(db_index=True)以提升查询效率
|
||||
# 作用: 案例的核心标识字段,用于列表展示和搜索
|
||||
name = models.CharField(
|
||||
null=False,
|
||||
max_length=100,
|
||||
db_index=True,
|
||||
verbose_name="案例名称",
|
||||
help_text="案例名称",
|
||||
db_comment='案例名称'
|
||||
db_comment='案例名称' # 数据库字段注释
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 案例图片
|
||||
# ----------------------------------------------------------
|
||||
# 字段说明: 案例的图片
|
||||
# 字段类型: 字符串(CharField)
|
||||
# 约束: 允许为空(null=True, blank=True),最大长度255字符
|
||||
# 存储: 存储的是相对路径,通过 get_file_url() 生成完整访问URL
|
||||
# 作用: 用于案例列表展示和详情页的图片
|
||||
avatar = models.CharField(
|
||||
null=True,
|
||||
blank=True,
|
||||
max_length=255,
|
||||
verbose_name="案例图片",
|
||||
help_text="案例图片",
|
||||
db_comment='案例图片'
|
||||
db_comment='案例图片' # 数据库字段注释
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 案例类型
|
||||
# ----------------------------------------------------------
|
||||
# 类型常量定义
|
||||
TYPE_CHOICES = (
|
||||
(1, "类型1"),
|
||||
(2, "类型2"),
|
||||
@@ -59,44 +87,98 @@ class Example(BaseModel):
|
||||
(4, "类型4"),
|
||||
)
|
||||
|
||||
# 字段说明: 案例的分类类型
|
||||
# 字段类型: 整数(IntegerField)
|
||||
# 约束: 非空(null=False)
|
||||
# 选择项: 使用 TYPE_CHOICES 限制可选值
|
||||
# 作用: 对案例进行分类管理,支持按类型筛选
|
||||
type = models.IntegerField(
|
||||
null=False,
|
||||
choices=TYPE_CHOICES,
|
||||
verbose_name="案例类型:1-类型1 2-类型2 3-类型3 4-类型4",
|
||||
help_text="案例类型:1-类型1 2-类型2 3-类型3 4-类型4",
|
||||
db_comment='案例类型:1-类型1 2-类型2 3-类型3 4-类型4'
|
||||
db_comment='案例类型:1-类型1 2-类型2 3-类型3 4-类型4' # 数据库字段注释
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 案例状态
|
||||
# ----------------------------------------------------------
|
||||
# 状态常量定义
|
||||
STATUS_CHOICES = (
|
||||
(1, "正常"),
|
||||
(2, "禁用"),
|
||||
(1, "正常"), # 正常状态:案例正常显示
|
||||
(2, "禁用"), # 禁用状态:案例不对外展示
|
||||
)
|
||||
|
||||
# 字段说明: 案例的显示状态
|
||||
# 字段类型: 整数(IntegerField)
|
||||
# 约束: 非空(null=False)
|
||||
# 选择项: 使用 STATUS_CHOICES 限制可选值
|
||||
# 作用: 控制案例是否在前端展示
|
||||
status = models.IntegerField(
|
||||
null=False,
|
||||
choices=STATUS_CHOICES,
|
||||
verbose_name="案例状态:1-正常 2-禁用",
|
||||
help_text="案例状态:1-正常 2-禁用",
|
||||
db_comment='案例状态:1-正常 2-禁用'
|
||||
db_comment='案例状态:1-正常 2-禁用' # 数据库字段注释
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 排序
|
||||
# ----------------------------------------------------------
|
||||
# 字段说明: 案例的排序权重
|
||||
# 字段类型: 整数(IntegerField)
|
||||
# 约束: 非空(null=False)
|
||||
# 排序规则: 数值越小越靠前(升序排列)
|
||||
# 作用: 手动控制案例在列表中的显示顺序
|
||||
sort = models.IntegerField(
|
||||
null=False,
|
||||
verbose_name="排序",
|
||||
help_text="排序",
|
||||
db_comment='排序'
|
||||
db_comment='排序' # 数据库字段注释
|
||||
)
|
||||
|
||||
# =============================================================
|
||||
# 模型元数据配置
|
||||
# =============================================================
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
模型元数据配置类
|
||||
|
||||
定义模型在数据库中的表名、显示名称、默认排序规则等。
|
||||
"""
|
||||
|
||||
# 数据表名
|
||||
# 使用配置文件中的表前缀 + "example",避免与其他应用表名冲突
|
||||
db_table = get_table_name('example')
|
||||
|
||||
# 表注释(Django 3.2+ 支持)
|
||||
# 在数据库中生成 COMMENT 语句,便于数据库管理
|
||||
db_table_comment = "案例表"
|
||||
|
||||
# 模型在admin后台显示的友好名称(单数)
|
||||
verbose_name = "案例表"
|
||||
|
||||
# 模型在admin后台显示的友好名称(复数)
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
# 默认排序规则
|
||||
# 按照 sort 字段升序排列(数值小的在前),确保显示顺序可控
|
||||
ordering = ("sort",)
|
||||
|
||||
# =============================================================
|
||||
# 实例方法
|
||||
# =============================================================
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
模型的字符串表示方法
|
||||
|
||||
当需要将模型实例转换为字符串时调用,如admin后台显示、日志输出等。
|
||||
|
||||
Returns:
|
||||
str: 包含案例名称的字符串
|
||||
"""
|
||||
return f"案例{self.name}"
|
||||
```
|
||||
|
||||
|
||||
@@ -17,30 +17,90 @@ application/urls.py # 主路由
|
||||
from django.urls import path
|
||||
from application.example import views
|
||||
|
||||
"""
|
||||
案例模块路由配置
|
||||
=============================================================
|
||||
|
||||
URL端点概览:
|
||||
------------------------------------------------------------------------
|
||||
| 功能说明 | 请求方法 | URL路径 | 视图类 |
|
||||
|-----------------|----------|-----------------------------|-------------------------|
|
||||
| 分页查询 | GET | /example/page | ExamplePageView |
|
||||
| 列表查询 | GET | /example/list | ExampleListView |
|
||||
| 详情查询 | GET | /example/detail/<id> | ExampleDetailView |
|
||||
| 添加案例 | POST | /example/add | ExampleAddView |
|
||||
| 更新案例 | PUT | /example/update | ExampleUpdateView |
|
||||
| 删除案例 | DELETE | /example/delete/<ids>| ExampleDeleteView |
|
||||
| 设置状态 | PUT | /example/status | ExampleStatusView |
|
||||
| 获取列表 | GET | /example/data | ExampleDataView |
|
||||
------------------------------------------------------------------------
|
||||
|
||||
URL参数说明:
|
||||
- id: int类型,案例ID(用于查询详情)
|
||||
- id: str类型,逗号分隔的案例ID字符串(用于批量删除)
|
||||
|
||||
使用示例:
|
||||
1. 分页查询:GET /api/example/page?pageNo=1&pageSize=10
|
||||
2. 列表查询:GET /api/example/list
|
||||
3. 详情查询:GET /api/example/detail/5
|
||||
4. 添加案例:POST /api/example/add
|
||||
5. 更新案例:PUT /api/example/update
|
||||
6. 删除案例:DELETE /api/example/delete/1,2,3
|
||||
7. 设置状态:PUT /api/example/status
|
||||
8. 获取列表:GET /api/example/data
|
||||
"""
|
||||
|
||||
# 案例模块路由配置
|
||||
urlpatterns = [
|
||||
# 查询案例分页列表
|
||||
# URL: /example/page
|
||||
# 请求方法: GET
|
||||
# 功能: 获取案例分页数据,支持按案例名称、案例类型、案例状态等条件筛选
|
||||
path('page', views.ExamplePageView.as_view()),
|
||||
|
||||
# 查询案例数据列表(支持筛选)
|
||||
# URL: /example/list
|
||||
# 请求方法: GET
|
||||
# 功能: 获取所有案例的列表数据,支持按条件筛选,用于下拉选择等前端组件
|
||||
path('list', views.ExampleListView.as_view()),
|
||||
|
||||
# 查询案例详情
|
||||
# URL: /example/detail/<int:id>
|
||||
# 请求方法: GET
|
||||
# URL参数: id - 案例ID(整数类型)
|
||||
# 功能: 根据ID获取指定案例的详细信息
|
||||
path('detail/<int:id>', views.ExampleDetailView.as_view()),
|
||||
|
||||
# 添加案例
|
||||
# URL: /example/add
|
||||
# 请求方法: POST
|
||||
# 功能: 创建新的案例记录,请求体需包含案例信息(name、avatar、type、status、sort等)
|
||||
path('add', views.ExampleAddView.as_view()),
|
||||
|
||||
# 更新案例
|
||||
# URL: /example/update
|
||||
# 请求方法: PUT
|
||||
# 功能: 更新现有案例信息,请求体需包含案例ID和更新内容
|
||||
path('update', views.ExampleUpdateView.as_view()),
|
||||
|
||||
# 删除案例
|
||||
# URL: /example/delete/<str:id>
|
||||
# 请求方法: DELETE
|
||||
# URL参数: id - 案例ID字符串,支持单个ID或逗号分隔的多个ID(如 "1,2,3")
|
||||
# 功能: 删除指定案例(逻辑删除,设置is_delete=True)
|
||||
path('delete/<str:id>', views.ExampleDeleteView.as_view()),
|
||||
|
||||
# 设置案例状态
|
||||
# URL: /example/status
|
||||
# 请求方法: PUT
|
||||
# 功能: 更新案例的启用/禁用状态
|
||||
path('status', views.ExampleStatusView.as_view()),
|
||||
|
||||
# 查询案例数据列表
|
||||
path('list', views.ExampleDataView.as_view()),
|
||||
# URL: /example/data
|
||||
# 请求方法: GET
|
||||
# 功能: 获取所有启用案例的列表数据,用于下拉选择等前端组件
|
||||
path('data', views.ExampleDataView.as_view()),
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
@@ -23,93 +23,305 @@ from application.operation_log.constants import LogType
|
||||
from application.operation_log.decorators import operation_log
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 分页查询视图
|
||||
# =============================================================
|
||||
|
||||
@method_decorator(check_login, name="get")
|
||||
class ExamplePageView(PermissionRequired, View):
|
||||
"""案例分页数据查询视图"""
|
||||
"""
|
||||
案例分页数据查询视图
|
||||
|
||||
处理GET请求,返回案例列表的分页数据。
|
||||
支持按案例名称、案例类型、案例状态等条件筛选。
|
||||
|
||||
URL: /example/page
|
||||
请求方法: GET
|
||||
权限要求:sys:example:page
|
||||
"""
|
||||
|
||||
# 方法权限标识
|
||||
permission_required = ("sys:example:page",)
|
||||
|
||||
@operation_log(title="案例管理-查询分页列表", log_type=LogType.QUERY)
|
||||
@operation_log(
|
||||
title="案例管理-查询分页列表",
|
||||
log_type=LogType.QUERY
|
||||
)
|
||||
def get(self, request):
|
||||
"""
|
||||
处理GET请求,返回案例分页数据
|
||||
|
||||
Args:
|
||||
request: HttpRequest对象,包含分页参数和筛选条件
|
||||
- pageNo: 当前页码
|
||||
- pageSize: 每页记录数
|
||||
- name: 案例名称(可选,模糊查询)
|
||||
- type: 案例类型(可选,精确匹配)
|
||||
- status: 案例状态(可选,精确匹配)
|
||||
|
||||
Returns:
|
||||
JsonResponse: 包含分页数据的JSON响应
|
||||
"""
|
||||
result = services.get_example_page(request)
|
||||
return result
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 列表查询视图(不分页)
|
||||
# =============================================================
|
||||
|
||||
@method_decorator(check_login, name='get')
|
||||
class ExampleListView(View):
|
||||
"""案例列表获取视图(支持筛选,不分页)"""
|
||||
"""
|
||||
案例列表获取视图(支持筛选,不分页)
|
||||
|
||||
处理GET请求,返回所有案例的列表数据。
|
||||
支持按案例名称、案例类型、案例状态等条件筛选。
|
||||
|
||||
URL: /example/list
|
||||
请求方法: GET
|
||||
权限要求:需要登录
|
||||
"""
|
||||
|
||||
def get(self, request):
|
||||
"""
|
||||
处理GET请求,返回案例列表
|
||||
|
||||
Args:
|
||||
request: HttpRequest对象
|
||||
|
||||
Returns:
|
||||
JsonResponse: 包含案例列表的JSON响应
|
||||
"""
|
||||
result = services.get_example_list(request)
|
||||
return R.ok(data=result)
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 详情查询视图
|
||||
# =============================================================
|
||||
|
||||
@method_decorator(check_login, name="get")
|
||||
class ExampleDetailView(PermissionRequired, View):
|
||||
"""案例详情查询视图"""
|
||||
"""
|
||||
案例详情查询视图
|
||||
|
||||
处理GET请求,根据案例ID返回案例详细信息。
|
||||
|
||||
URL: /example/detail/<int:id>
|
||||
请求方法: GET
|
||||
权限要求:sys:example:detail
|
||||
"""
|
||||
|
||||
# 方法权限标识
|
||||
permission_required = ("sys:example:detail",)
|
||||
|
||||
@operation_log(title="案例管理-查询记录详情", log_type=LogType.QUERY)
|
||||
@operation_log(
|
||||
title="案例管理-查询记录详情",
|
||||
log_type=LogType.QUERY
|
||||
)
|
||||
def get(self, request, id):
|
||||
"""
|
||||
处理GET请求,返回案例详情
|
||||
|
||||
Args:
|
||||
request: HttpRequest对象
|
||||
id: 案例ID(URL参数)
|
||||
|
||||
Returns:
|
||||
JsonResponse: 包含案例详情的JSON响应
|
||||
"""
|
||||
data = services.get_example_detail(id)
|
||||
return R.ok(data=data)
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 添加视图
|
||||
# =============================================================
|
||||
|
||||
@method_decorator(check_login, name="post")
|
||||
class ExampleAddView(PermissionRequired, View):
|
||||
"""添加案例视图"""
|
||||
"""
|
||||
添加案例视图
|
||||
|
||||
处理POST请求,创建新的案例记录。
|
||||
请求体需包含案例信息(name、avatar、type、status、sort等)。
|
||||
|
||||
URL: /example/add
|
||||
请求方法: POST
|
||||
权限要求:sys:example:add
|
||||
"""
|
||||
|
||||
# 方法权限标识
|
||||
permission_required = ("sys:example:add",)
|
||||
|
||||
@operation_log(title="案例管理-添加记录", log_type=LogType.ADD)
|
||||
@operation_log(
|
||||
title="案例管理-添加记录",
|
||||
log_type=LogType.ADD
|
||||
)
|
||||
def post(self, request):
|
||||
"""
|
||||
处理POST请求,添加案例
|
||||
|
||||
Args:
|
||||
request: HttpRequest对象,请求体包含案例信息
|
||||
|
||||
Returns:
|
||||
JsonResponse: 操作结果的JSON响应
|
||||
"""
|
||||
# 演示模式检查
|
||||
if DJANGO_DEMO:
|
||||
return R.failed("演示环境,暂无操作权限")
|
||||
result = services.add_example(request)
|
||||
return result
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 更新视图
|
||||
# =============================================================
|
||||
|
||||
@method_decorator(check_login, name="put")
|
||||
class ExampleUpdateView(PermissionRequired, View):
|
||||
"""更新案例视图"""
|
||||
"""
|
||||
更新案例视图
|
||||
|
||||
处理PUT请求,更新现有案例信息。
|
||||
请求体需包含案例ID和更新内容。
|
||||
|
||||
URL: /example/update
|
||||
请求方法: PUT
|
||||
权限要求:sys:example:update
|
||||
"""
|
||||
|
||||
# 方法权限标识
|
||||
permission_required = ('sys:example:update',)
|
||||
|
||||
@operation_log(title="案例管理-更新记录", log_type=LogType.UPDATE)
|
||||
@operation_log(
|
||||
title="案例管理-更新记录",
|
||||
log_type=LogType.UPDATE
|
||||
)
|
||||
def put(self, request):
|
||||
"""
|
||||
处理PUT请求,更新案例
|
||||
|
||||
Args:
|
||||
request: HttpRequest对象,请求体包含更新信息
|
||||
|
||||
Returns:
|
||||
JsonResponse: 操作结果的JSON响应
|
||||
"""
|
||||
# 演示模式检查
|
||||
if DJANGO_DEMO:
|
||||
return R.failed("演示环境,暂无操作权限")
|
||||
result = services.update_example(request)
|
||||
return result
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 删除视图
|
||||
# =============================================================
|
||||
|
||||
@method_decorator(check_login, name="delete")
|
||||
class ExampleDeleteView(PermissionRequired, View):
|
||||
"""删除案例视图"""
|
||||
"""
|
||||
删除案例视图
|
||||
|
||||
处理DELETE请求,删除指定案例(逻辑删除)。
|
||||
支持批量删除,多个ID用逗号分隔。
|
||||
|
||||
URL: /example/delete/<str:id>
|
||||
请求方法: DELETE
|
||||
权限要求:sys:example:delete
|
||||
"""
|
||||
|
||||
# 方法权限标识
|
||||
permission_required = ('sys:example:delete',)
|
||||
|
||||
@operation_log(title="案例管理-删除记录", log_type=LogType.DELETE)
|
||||
@operation_log(
|
||||
title="案例管理-删除记录",
|
||||
log_type=LogType.DELETE
|
||||
)
|
||||
def delete(self, request, id):
|
||||
"""
|
||||
处理DELETE请求,删除案例
|
||||
|
||||
Args:
|
||||
request: HttpRequest对象
|
||||
id: 案例ID字符串,支持逗号分隔的多个ID(如 "1,2,3")
|
||||
|
||||
Returns:
|
||||
JsonResponse: 操作结果的JSON响应
|
||||
"""
|
||||
# 演示模式检查
|
||||
if DJANGO_DEMO:
|
||||
return R.failed("演示环境,暂无操作权限")
|
||||
result = services.delete_examples(id)
|
||||
return result
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 状态更新视图
|
||||
# =============================================================
|
||||
|
||||
@method_decorator(check_login, name="put")
|
||||
class ExampleStatusView(PermissionRequired, View):
|
||||
"""设置案例状态视图"""
|
||||
"""
|
||||
设置案例状态视图
|
||||
|
||||
处理PUT请求,更新案例的启用/禁用状态。
|
||||
|
||||
URL: /example/status
|
||||
请求方法: PUT
|
||||
权限要求:sys:example:status
|
||||
"""
|
||||
|
||||
# 方法权限标识
|
||||
permission_required = ('sys:example:status',)
|
||||
|
||||
def put(self, request):
|
||||
"""
|
||||
处理PUT请求,更新案例状态
|
||||
|
||||
Args:
|
||||
request: HttpRequest对象,请求体包含案例ID和新状态
|
||||
|
||||
Returns:
|
||||
JsonResponse: 操作结果的JSON响应
|
||||
"""
|
||||
# 演示模式检查
|
||||
if DJANGO_DEMO:
|
||||
return R.failed("演示环境,暂无操作权限")
|
||||
result = services.update_example_status(request)
|
||||
return result
|
||||
|
||||
|
||||
# =============================================================
|
||||
# 数据列表视图(下拉选择用)
|
||||
# =============================================================
|
||||
|
||||
@method_decorator(check_login, name='get')
|
||||
class ExampleDataView(View):
|
||||
"""案例列表获取视图"""
|
||||
"""
|
||||
案例列表获取视图
|
||||
|
||||
处理GET请求,返回所有启用案例的列表数据。
|
||||
用于下拉选择等前端组件。
|
||||
|
||||
URL: /example/data
|
||||
请求方法: GET
|
||||
权限要求:需要登录
|
||||
"""
|
||||
|
||||
def get(self, request):
|
||||
"""
|
||||
处理GET请求,返回案例数据列表
|
||||
|
||||
Args:
|
||||
request: HttpRequest对象
|
||||
|
||||
Returns:
|
||||
JsonResponse: 包含案例列表的JSON响应
|
||||
"""
|
||||
result = services.get_example_data()
|
||||
return R.ok(data=result)
|
||||
```
|
||||
|
||||
@@ -53,65 +53,100 @@ JsonResponse <-- Django HTTP响应
|
||||
|
||||
```python
|
||||
class BaseModel(models.Model):
|
||||
"""基础模型抽象类"""
|
||||
"""
|
||||
基础模型抽象类
|
||||
==============
|
||||
功能:提供所有模型的公共字段,如主键、创建人、创建时间等
|
||||
特点:定义为抽象类,不会在数据库中创建对应的表
|
||||
用途:被其他业务模型继承,实现字段复用
|
||||
|
||||
继承此基类的模型会自动拥有以下6个公共字段:
|
||||
- id: 自增主键
|
||||
- create_user: 创建人
|
||||
- create_time: 创建时间
|
||||
- update_user: 更新人
|
||||
- update_time: 更新时间
|
||||
- is_delete: 逻辑删除标识
|
||||
|
||||
注意:公共字段会自动排列在业务字段之后,使数据库表结构更清晰
|
||||
"""
|
||||
|
||||
# ==================== 公共字段定义 ====================
|
||||
|
||||
# 主键ID字段
|
||||
# 自增主键,每个模型的唯一标识符
|
||||
id = models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='主键ID',
|
||||
db_comment='主键ID'
|
||||
auto_created=True, # 自动创建主键字段
|
||||
primary_key=True, # 设置为主键
|
||||
serialize=False, # 不参与序列化(避免在API响应中暴露)
|
||||
verbose_name='主键ID', # 字段中文名称
|
||||
db_comment='主键ID' # 数据库字段注释
|
||||
)
|
||||
|
||||
# 创建人字段
|
||||
# 记录数据由谁创建,通常关联用户表的用户名或ID
|
||||
# 例如:admin, zhangsan, system
|
||||
create_user = models.CharField(
|
||||
null=True,
|
||||
max_length=50,
|
||||
default=None,
|
||||
db_default=None,
|
||||
verbose_name='创建人',
|
||||
db_comment='创建人'
|
||||
null=True, # 数据库允许为NULL
|
||||
max_length=50, # 最大长度50个字符
|
||||
default=None, # 默认值为None
|
||||
db_default=None, # 数据库层默认值
|
||||
verbose_name='创建人', # 字段中文名称
|
||||
db_comment='创建人' # 数据库字段注释
|
||||
)
|
||||
|
||||
# 创建时间字段
|
||||
# 自动记录数据创建的时间戳
|
||||
# auto_now_add: 仅在创建时自动设置当前时间,后续更新不会改变
|
||||
create_time = models.DateTimeField(
|
||||
null=True,
|
||||
auto_now_add=True,
|
||||
verbose_name="创建时间",
|
||||
max_length=11,
|
||||
db_comment='创建时间'
|
||||
null=True, # 数据库允许为NULL
|
||||
auto_now_add=True, # 创建时自动设为当前时间
|
||||
verbose_name="创建时间", # 字段中文名称
|
||||
max_length=11, # 时间戳长度
|
||||
db_comment='创建时间' # 数据库字段注释
|
||||
)
|
||||
|
||||
# 更新人字段
|
||||
# 记录数据最后由谁更新,通常关联用户表的用户名或ID
|
||||
# null=True: 允许数据库为NULL(新创建记录时可能没有更新人)
|
||||
update_user = models.CharField(
|
||||
null=True,
|
||||
max_length=50,
|
||||
default=None,
|
||||
db_default=None,
|
||||
verbose_name='更新人',
|
||||
db_comment='更新人'
|
||||
null=True, # 数据库允许为NULL
|
||||
max_length=50, # 最大长度50个字符
|
||||
default=None, # 默认值为None
|
||||
db_default=None, # 数据库层默认值
|
||||
verbose_name='更新人', # 字段中文名称
|
||||
db_comment='更新人' # 数据库字段注释
|
||||
)
|
||||
|
||||
# 更新时间字段
|
||||
# 自动记录数据最后更新的时间戳
|
||||
# auto_now: 每次保存(更新)时自动设为当前时间
|
||||
update_time = models.DateTimeField(
|
||||
null=True,
|
||||
auto_now=True,
|
||||
verbose_name="更新时间",
|
||||
max_length=11,
|
||||
db_comment='更新时间'
|
||||
null=True, # 数据库允许为NULL
|
||||
auto_now=True, # 每次保存时自动更新为当前时间
|
||||
verbose_name="更新时间", # 字段中文名称
|
||||
max_length=11, # 时间戳长度
|
||||
db_comment='更新时间' # 数据库字段注释
|
||||
)
|
||||
|
||||
# 逻辑删除标识
|
||||
# 用于软删除,避免物理删除数据造成的数据丢失
|
||||
# 0:正常(默认值)| 1:已删除
|
||||
is_delete = models.BooleanField(
|
||||
default=0,
|
||||
db_default=0,
|
||||
verbose_name="逻辑删除",
|
||||
db_comment='逻辑删除:0-正常 1-已删除'
|
||||
default=0, # 默认为0,表示正常
|
||||
db_default=0, # 数据库层默认值
|
||||
verbose_name="逻辑删除", # 字段中文名称
|
||||
db_comment='逻辑删除:0-正常 1-已删除' # 数据库字段注释
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True # 抽象模型,不创建数据库表
|
||||
"""
|
||||
元数据配置
|
||||
"""
|
||||
# 定义为抽象模型类
|
||||
# abstract = True 表示这个模型是抽象的,不会在数据库中创建对应的表
|
||||
# 子类模型会继承这些字段,并在各自对应的表中创建这些列
|
||||
abstract = True
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
|
||||
@@ -65,7 +65,7 @@ REDIS_DB = int(os.getenv('REDIS_DB', 0))
|
||||
|------|--------|------|
|
||||
| `REDIS_HOST` | `127.0.0.1` | Redis服务器地址 |
|
||||
| `REDIS_PORT` | `6379` | Redis端口 |
|
||||
| `REDIS_PASSWORD` | `123456` | Redis密码 |
|
||||
| `REDIS_PASSWORD` | `123456` | Redis密码(`.env` 中配置,无密码时留空) |
|
||||
| `REDIS_DB` | `0` | Redis数据库索引(0-15) |
|
||||
|
||||
## 4.3 缓存应用场景
|
||||
|
||||
@@ -162,7 +162,7 @@ public/templates/
|
||||
|
||||
### 4.4.2 模板语法示例
|
||||
|
||||
```jinja2
|
||||
```jinja
|
||||
{# models.py.tpl #}
|
||||
from django.db import models
|
||||
from application.models import BaseModel
|
||||
@@ -294,7 +294,6 @@ python manage.py runserver # 启动开发服务器
|
||||
python manage.py makemigrations <app> # 生成迁移文件
|
||||
python manage.py migrate # 执行迁移
|
||||
python manage.py createsuperuser # 创建超级用户
|
||||
python manage.py add_full_comments # 回填数据库字段注释
|
||||
```
|
||||
|
||||
## 4.8 前端扩展
|
||||
|
||||
@@ -212,7 +212,7 @@ import(/* webpackChunkName: "editor" */ '@wangeditor/editor');
|
||||
|
||||
## Gunicorn Worker 配置
|
||||
|
||||
```ini
|
||||
```bash
|
||||
# gunicorn.conf.py
|
||||
import multiprocessing
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ Jinja2 模板文件,分为后端模板(7 个)和前端模板(ui/ 6 个 +
|
||||
## 使用限制
|
||||
|
||||
:::warning 使用前提
|
||||
1. **数据库要求**:代码生成器基于 MySQL 的 `information_schema` 查询表结构,仅支持 MySQL 数据库。
|
||||
1. **数据库要求**:代码生成器基于 MySQL 的 `information_schema` 查询表结构,仅支持 MySQL 数据库。其他数据库(PostgreSQL、Oracle、SQL Server 等)需手动创建模块。
|
||||
2. **表结构要求**:不支持复合主键、外键关联等复杂表结构,建议使用单主键(自增 id)的标准业务表。
|
||||
3. **环境要求**:需确保 Django 环境已初始化、MySQL 和 Redis 服务正常运行。
|
||||
:::
|
||||
|
||||
@@ -162,7 +162,7 @@ public/templates/
|
||||
|
||||
修改 `public/templates/models.py.tpl`:
|
||||
|
||||
```jinja2
|
||||
```jinja
|
||||
{% if f.type == 'CharField' %}
|
||||
{{ f.name }} = models.CharField(
|
||||
max_length={{ f.max_length or 255 }},
|
||||
@@ -177,7 +177,7 @@ public/templates/
|
||||
|
||||
修改 `public/templates/services.py.tpl`,在添加方法中添加自定义校验:
|
||||
|
||||
```jinja2
|
||||
```jinja
|
||||
def add_{{ app_name }}(request):
|
||||
# ... 原有代码 ...
|
||||
# 自定义校验:检查名称是否重复
|
||||
@@ -190,7 +190,7 @@ def add_{{ app_name }}(request):
|
||||
|
||||
修改 `public/templates/ui/columns.ts.tpl`:
|
||||
|
||||
```jinja2
|
||||
```jinja
|
||||
{% for f in list_fields %}
|
||||
{
|
||||
label: '{{ f.label }}',
|
||||
@@ -208,7 +208,7 @@ def add_{{ app_name }}(request):
|
||||
|
||||
在 `public/templates/` 下新建 `test.py.tpl`:
|
||||
|
||||
```jinja2
|
||||
```jinja
|
||||
# tests/test_{{ app_name }}.py
|
||||
from django.test import TestCase
|
||||
from application.{{ app_name }}.models import {{ model_class_name }}
|
||||
@@ -247,7 +247,7 @@ backend_templates = [
|
||||
|
||||
## Jinja2 语法速查
|
||||
|
||||
```jinja2
|
||||
```jinja
|
||||
{# 注释 #}
|
||||
|
||||
{# 变量输出 #}
|
||||
|
||||
@@ -44,7 +44,7 @@ mysqlbinlog --start-datetime="2026-09-05 00:00:00" \
|
||||
|
||||
:::tip binlog 配置
|
||||
确保 MySQL 配置中启用了 binlog:
|
||||
```ini
|
||||
```bash
|
||||
[mysqld]
|
||||
server-id = 1
|
||||
log-bin = mysql-bin
|
||||
|
||||
@@ -167,7 +167,7 @@ stages:
|
||||
|
||||
### pytest 配置
|
||||
|
||||
```ini
|
||||
```bash
|
||||
# pytest.ini
|
||||
[pytest]
|
||||
DJANGO_SETTINGS_MODULE = application.settings
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
:::tip 迁移工具链
|
||||
- **结构迁移**:Django 内置迁移系统(`makemigrations` + `migrate`)
|
||||
- **字段注释**:Django 4.1+ 通过 `db_comment` 参数自动添加数据库字段注释
|
||||
- **自定义命令**:`python manage.py add_full_comments` 回填数据库字段注释
|
||||
:::
|
||||
|
||||
## 全新安装初始化
|
||||
@@ -99,7 +98,7 @@ python manage.py migrate --fake example
|
||||
|
||||
## 字段注释管理
|
||||
|
||||
Django 4.1+ 支持通过 `db_comment` 参数为数据库字段添加注释。项目中的模型已自动生成 `db_comment`:
|
||||
Django 4.1+ 支持通过 `db_comment` 参数为数据库字段添加注释。项目中的模型已自动生成 `db_comment`,在执行 `migrate` 时会自动同步到数据库:
|
||||
|
||||
```python
|
||||
class Example(BaseModel):
|
||||
@@ -110,16 +109,6 @@ class Example(BaseModel):
|
||||
)
|
||||
```
|
||||
|
||||
### 回填字段注释
|
||||
|
||||
对于已有数据库,可使用自定义命令回填字段注释:
|
||||
|
||||
```bash
|
||||
python manage.py add_full_comments
|
||||
```
|
||||
|
||||
该命令会遍历所有模型,为缺少注释的字段补充 `db_comment`。
|
||||
|
||||
## 迁移文件结构
|
||||
|
||||
每个应用的迁移文件位于 `application/{app_name}/migrations/` 目录:
|
||||
@@ -150,7 +139,6 @@ application/example/
|
||||
| 回滚指定应用 | `python manage.py migrate <app> zero` |
|
||||
| 查看迁移状态 | `python manage.py showmigrations` |
|
||||
| 查看迁移 SQL | `python manage.py sqlmigrate <app> <migration>` |
|
||||
| 回填字段注释 | `python manage.py add_full_comments` |
|
||||
|
||||
## 常见问题与排错
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ class MasterSlaveRouter:
|
||||
|
||||
## Supervisor 多实例配置
|
||||
|
||||
```ini
|
||||
```bash
|
||||
; /etc/supervisor/conf.d/djangoadmin.conf
|
||||
[program:djangoadmin-1]
|
||||
command=/opt/venv/bin/gunicorn application.wsgi:application -c gunicorn.conf.py -b 127.0.0.1:8001
|
||||
|
||||
@@ -12,7 +12,7 @@ Supervisor 是一个 Python 编写的进程管理工具,用于在 Linux 服务
|
||||
|
||||
## 配置文件示例
|
||||
|
||||
```ini
|
||||
```bash
|
||||
; Supervisor 进程管理配置
|
||||
; 将 command / directory 替换为实际路径后复制到 /etc/supervisor/conf.d/
|
||||
|
||||
@@ -134,7 +134,7 @@ gunicorn application.wsgi:application \
|
||||
|
||||
Supervisor 内置日志轮转功能,通过以下配置控制:
|
||||
|
||||
```ini
|
||||
```bash
|
||||
; 单个日志文件最大 50MB
|
||||
stdout_logfile_maxbytes=50MB
|
||||
; 保留 10 个历史日志文件
|
||||
@@ -153,7 +153,7 @@ stdout_logfile_backups=10
|
||||
|
||||
如需同时管理多个进程(如应用 + Celery Worker):
|
||||
|
||||
```ini
|
||||
```bash
|
||||
[program:djangoadmin]
|
||||
command=/path/to/venv/bin/gunicorn application.wsgi:application --bind 127.0.0.1:8000 --workers 4
|
||||
directory=/data/apps/DjangoAdmin_Django_EleVue
|
||||
|
||||
@@ -67,83 +67,85 @@
|
||||
|
||||
| 版本名称 | 说明 | 地址 |
|
||||
|:---------|:-----|:-----|
|
||||
| Django+Layui混编版 | 基于 Django、Jinja2、Layui、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_Layui) |
|
||||
| Django+EleVue前后端分离版 | 基于 Django、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_EleVue) |
|
||||
| Django+AntdVue前后端分离版 | 基于 Django、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_AntdVue) |
|
||||
| Django+NaiveVue前后端分离版 | 基于 Django、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_NaiveVue) |
|
||||
| Flask+Layui混编版 | 基于 Flask、Jinja2、Layui、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_Layui) |
|
||||
| Flask+EleVue前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_EleVue) |
|
||||
| Flask+AntdVue前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_AntdVue) |
|
||||
| Flask+NaiveVue前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_NaiveVue) |
|
||||
| FastAPI+Layui混编版 | 基于 FastAPI、Jinja2、Layui、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_Layui) |
|
||||
| FastAPI+EleVue前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_EleVue) |
|
||||
| FastAPI+AntdVue前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_AntdVue) |
|
||||
| FastAPI+NaiveVue前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_NaiveVue) |
|
||||
| Tornado+Layui混编版 | 基于 Tornado、Jinja2、Layui、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_Layui) |
|
||||
| Tornado+EleVue前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_EleVue) |
|
||||
| Tornado+AntdVue前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_AntdVue) |
|
||||
| Tornado+NaiveVue前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_NaiveVue) |
|
||||
| Django+Layui混编版 | 基于 Django、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_Layui) |
|
||||
| Django+ElementPlus前后端分离版 | 基于 Django、Django ORM、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_EleVue) |
|
||||
| Django+AntDesign前后端分离版 | 基于 Django、Django ORM、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_AntdVue) |
|
||||
| Django+NaiveUI前后端分离版 | 基于 Django、Django ORM、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_NaiveVue) |
|
||||
| Flask+Layui混编版 | 基于 Flask、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_Layui) |
|
||||
| Flask+ElementPlus前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_EleVue) |
|
||||
| Flask+AntDesign前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_AntdVue) |
|
||||
| Flask+NaiveUI前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_NaiveVue) |
|
||||
| FastAPI+Layui混编版 | 基于 FastAPI、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_Layui) |
|
||||
| FastAPI+ElementPlus前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_EleVue) |
|
||||
| FastAPI+AntDesign前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_AntdVue) |
|
||||
| FastAPI+NaiveUI前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_NaiveVue) |
|
||||
| Tornado+Layui混编版 | 基于 Tornado、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_Layui) |
|
||||
| Tornado+ElementPlus前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_EleVue) |
|
||||
| Tornado+AntDesign前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_AntdVue) |
|
||||
| Tornado+NaiveUI前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_NaiveVue) |
|
||||
|
||||
|
||||
## 模块展示
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="/demo/1.png"/></td>
|
||||
<td><img src="/demo/2.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/3.png"/></td>
|
||||
<td><img src="/demo/4.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/5.png"/></td>
|
||||
<td><img src="/demo/6.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/7.png"/></td>
|
||||
<td><img src="/demo/8.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/9.png"/></td>
|
||||
<td><img src="/demo/10.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/11.png"/></td>
|
||||
<td><img src="/demo/12.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/13.png"/></td>
|
||||
<td><img src="/demo/14.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/15.png"/></td>
|
||||
<td><img src="/demo/16.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/17.png"/></td>
|
||||
<td><img src="/demo/18.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/19.png"/></td>
|
||||
<td><img src="/demo/20.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/21.png"/></td>
|
||||
<td><img src="/demo/22.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/23.png"/></td>
|
||||
<td><img src="/demo/24.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/25.png"/></td>
|
||||
<td><img src="/demo/26.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/27.png"/></td>
|
||||
<td><img src="/demo/28.png"/></td>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><img src="/demo/1.png"/></td>
|
||||
<td><img src="/demo/2.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/3.png"/></td>
|
||||
<td><img src="/demo/4.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/5.png"/></td>
|
||||
<td><img src="/demo/6.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/7.png"/></td>
|
||||
<td><img src="/demo/8.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/9.png"/></td>
|
||||
<td><img src="/demo/10.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/11.png"/></td>
|
||||
<td><img src="/demo/12.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/13.png"/></td>
|
||||
<td><img src="/demo/14.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/15.png"/></td>
|
||||
<td><img src="/demo/16.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/17.png"/></td>
|
||||
<td><img src="/demo/18.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/19.png"/></td>
|
||||
<td><img src="/demo/20.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/21.png"/></td>
|
||||
<td><img src="/demo/22.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/23.png"/></td>
|
||||
<td><img src="/demo/24.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/25.png"/></td>
|
||||
<td><img src="/demo/26.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/27.png"/></td>
|
||||
<td><img src="/demo/28.png"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## 开源协议
|
||||
|
||||
@@ -67,83 +67,85 @@
|
||||
|
||||
| 版本名称 | 说明 | 地址 |
|
||||
|:---------|:-----|:-----|
|
||||
| Django+Layui混编版 | 基于 Django、Jinja2、Layui、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_Layui) |
|
||||
| Django+EleVue前后端分离版 | 基于 Django、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_EleVue) |
|
||||
| Django+AntdVue前后端分离版 | 基于 Django、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_AntdVue) |
|
||||
| Django+NaiveVue前后端分离版 | 基于 Django、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_NaiveVue) |
|
||||
| Flask+Layui混编版 | 基于 Flask、Jinja2、Layui、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_Layui) |
|
||||
| Flask+EleVue前后端分离版 | 基于 Flask、SQLAlchemy、WTForms、Vue3、ElementPlus、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_EleVue) |
|
||||
| Flask+AntdVue前后端分离版 | 基于 Flask、SQLAlchemy、WTForms、Vue3、AntDesign、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_AntdVue) |
|
||||
| Flask+NaiveVue前后端分离版 | 基于 Flask、SQLAlchemy、WTForms、Vue3、NaiveUI、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_NaiveVue) |
|
||||
| FastAPI+Layui混编版 | 基于 FastAPI、Jinja2、Layui、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_Layui) |
|
||||
| FastAPI+EleVue前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_EleVue) |
|
||||
| FastAPI+AntdVue前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_AntdVue) |
|
||||
| FastAPI+NaiveVue前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_NaiveVue) |
|
||||
| Tornado+Layui混编版 | 基于 Tornado、Jinja2、Layui、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_Layui) |
|
||||
| Tornado+EleVue前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_EleVue) |
|
||||
| Tornado+AntdVue前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_AntdVue) |
|
||||
| Tornado+NaiveVue前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite、MySQL 等技术栈 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_NaiveVue) |
|
||||
| Django+Layui混编版 | 基于 Django、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_Layui) |
|
||||
| Django+ElementPlus前后端分离版 | 基于 Django、Django ORM、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_EleVue) |
|
||||
| Django+AntDesign前后端分离版 | 基于 Django、Django ORM、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_AntdVue) |
|
||||
| Django+NaiveUI前后端分离版 | 基于 Django、Django ORM、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Django_NaiveVue) |
|
||||
| Flask+Layui混编版 | 基于 Flask、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_Layui) |
|
||||
| Flask+ElementPlus前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_EleVue) |
|
||||
| Flask+AntDesign前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_AntdVue) |
|
||||
| Flask+NaiveUI前后端分离版 | 基于 Flask、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_NaiveVue) |
|
||||
| FastAPI+Layui混编版 | 基于 FastAPI、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_Layui) |
|
||||
| FastAPI+ElementPlus前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_EleVue) |
|
||||
| FastAPI+AntDesign前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_AntdVue) |
|
||||
| FastAPI+NaiveUI前后端分离版 | 基于 FastAPI、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_NaiveVue) |
|
||||
| Tornado+Layui混编版 | 基于 Tornado、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_Layui) |
|
||||
| Tornado+ElementPlus前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_EleVue) |
|
||||
| Tornado+AntDesign前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_AntdVue) |
|
||||
| Tornado+NaiveUI前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Tornado_NaiveVue) |
|
||||
|
||||
|
||||
## 模块展示
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="/demo/1.png"/></td>
|
||||
<td><img src="/demo/2.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/3.png"/></td>
|
||||
<td><img src="/demo/4.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/5.png"/></td>
|
||||
<td><img src="/demo/6.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/7.png"/></td>
|
||||
<td><img src="/demo/8.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/9.png"/></td>
|
||||
<td><img src="/demo/10.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/11.png"/></td>
|
||||
<td><img src="/demo/12.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/13.png"/></td>
|
||||
<td><img src="/demo/14.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/15.png"/></td>
|
||||
<td><img src="/demo/16.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/17.png"/></td>
|
||||
<td><img src="/demo/18.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/19.png"/></td>
|
||||
<td><img src="/demo/20.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/21.png"/></td>
|
||||
<td><img src="/demo/22.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/23.png"/></td>
|
||||
<td><img src="/demo/24.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/25.png"/></td>
|
||||
<td><img src="/demo/26.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/27.png"/></td>
|
||||
<td><img src="/demo/28.png"/></td>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><img src="/demo/1.png"/></td>
|
||||
<td><img src="/demo/2.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/3.png"/></td>
|
||||
<td><img src="/demo/4.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/5.png"/></td>
|
||||
<td><img src="/demo/6.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/7.png"/></td>
|
||||
<td><img src="/demo/8.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/9.png"/></td>
|
||||
<td><img src="/demo/10.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/11.png"/></td>
|
||||
<td><img src="/demo/12.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/13.png"/></td>
|
||||
<td><img src="/demo/14.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/15.png"/></td>
|
||||
<td><img src="/demo/16.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/17.png"/></td>
|
||||
<td><img src="/demo/18.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/19.png"/></td>
|
||||
<td><img src="/demo/20.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/21.png"/></td>
|
||||
<td><img src="/demo/22.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/23.png"/></td>
|
||||
<td><img src="/demo/24.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/25.png"/></td>
|
||||
<td><img src="/demo/26.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="/demo/27.png"/></td>
|
||||
<td><img src="/demo/28.png"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## 开源协议
|
||||
|
||||
Reference in New Issue
Block a user