签入版本

This commit is contained in:
2026-09-16 16:30:26 +08:00
parent c97d87847f
commit 67b91442fb
52 changed files with 2089 additions and 412 deletions
@@ -29,7 +29,7 @@
//
// themeConfig: {
// editLink: {
// pattern: "https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_EleVue",
// pattern: "https://gitee.com/djangoadmin/DjangoAdmin_Tornado_AntdVue",
// text: "Suggest changes to this page",
// },
//
@@ -87,7 +87,7 @@ export const sharedConfig = defineConfig({
socialLinks: [
{
icon: "github",
link: "https://gitee.com/djangoadmin/DjangoAdmin_Tornado_EleVue",
link: "https://gitee.com/djangoadmin/DjangoAdmin_Tornado_AntdVue",
},
],
@@ -98,7 +98,7 @@ export const sharedConfig = defineConfig({
},
editLink: {
pattern: "https://gitee.com/djangoadmin/DjangoAdmin_Tornado_EleVue",
pattern: "https://gitee.com/djangoadmin/DjangoAdmin_Tornado_AntdVue",
text: "对本页提出修改建议",
},
@@ -19,7 +19,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?f071c8d09d195a85c543afe1e419c5d5";
hm.src = "https://hm.baidu.com/hm.js?a2eba4ffde473afa20619e5322b30ed6";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
@@ -29,7 +29,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
themeConfig: {
editLink: {
pattern: "https://gitee.com/djangoadmin/DjangoAdmin_Tornado_EleVue",
pattern: "https://gitee.com/djangoadmin/DjangoAdmin_Tornado_AntdVue",
text: "对本页提出修改建议",
},
@@ -44,7 +44,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
nav: [
{ text: "官网", link: "https://www.djangoadmin.cn" },
{ text: "授权", link: "https://www.djangoadmin.cn/goods/detail/2" },
{ text: "授权", link: "https://www.djangoadmin.cn/goods/detail/3" },
{
text: "部署文档",
items: [
@@ -207,8 +207,8 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
link: "https://cn.vuejs.org/",
},
{
text: "ElementPlus",
link: "https://element-plus.org/zh-CN/",
text: "AntDesign Vue",
link: "https://antdv.com/components/overview-cn",
},
],
},
@@ -565,7 +565,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
},
// ------------------------------------------------------------
// 5.4 前端开发(Vue3 + Element Plus)
// 5.4 前端开发(Vue3 + AntDesign)
// ------------------------------------------------------------
{
text: "5.4 前端开发",
@@ -811,7 +811,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
{ text: "9.2 免责声明", link: "/zh/other/declare.html" },
{
text: "9.3 开源协议",
link: "https://gitee.com/djangoadmin/DjangoAdmin_Tornado_EleVue/blob/master/LICENSE",
link: "https://gitee.com/djangoadmin/DjangoAdmin_Tornado_AntdVue/blob/master/LICENSE",
},
{ text: "9.4 广告赞助", link: "/zh/other/sponsor.html" },
],
+5 -5
View File
@@ -2,11 +2,11 @@
layout: home
title: DjangoAdmin文档
titleTemplate: 前后端分离Tornado+ElementPlus版
titleTemplate: 前后端分离Tornado+AntDesign版
hero:
name: DjangoAdmin
text: "企业级中台解决方案Tornado+ElementPlus版"
text: "企业级中台解决方案Tornado+AntDesign版"
tagline: "赋能开发者,助力企业发展。\n企业级数据平台底座 · 让数字化触手可及。"
image:
src: /logo.svg
@@ -17,10 +17,10 @@ hero:
link: /zh/introduction
- theme: alt
text: 在线演示
link: http://manage.tornado.elevue.djangoadmin.cn
link: http://manage.tornado.antdvue.djangoadmin.cn
- theme: alt
text: 官方授权
link: https://www.djangoadmin.cn/goods/detail/2
link: https://www.djangoadmin.cn/goods/detail/3
- theme: alt
text: ❤️ 成为赞助商
link: /zh/other/sponsor.html
@@ -37,7 +37,7 @@ features:
linkText: 查看更多
rel: noreferrer
- title: 🚀 全新技术栈
details: Vue3、TypeScript、Vite、Pinia、Vue Router 等技术栈,采用主流前后端分离架构实现。
details: Tornado 6、SQLAlchemy 2.0、Pydantic v2、Vue3、TypeScript、Vite、Pinia、Vue Router 等技术栈,采用主流前后端分离架构实现。
link: /zh/volume3/tech-stack
linkText: 查看更多
rel: noreferrer
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -165,7 +165,7 @@
### 新增
- **Vue3 升级**:前端框架从 Vue 2 升级至 Vue3.4+,全面拥抱 Composition API
- **ElementPlus**:UI 组件库适配 Vue3 生态
- **AntDesign**:UI 组件库适配 Vue3 生态
- **Pinia 状态管理**:替代 Vuex,更轻量、更灵活、TypeScript 支持更完善
- **TypeScript 类型安全**:全量类型定义,IDE 智能提示与编译时类型检查
- **Vite 构建工具**:替代 Webpack,开发服务器毫秒级启动,HMR 即时生效
+3 -3
View File
@@ -85,7 +85,7 @@ cp .env.example .env
# 数据库配置
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=djangoadmin.tornado.elevue2
DB_DATABASE=djangoadmin.tornado.antdvue
DB_USERNAME=root
DB_PASSWORD=your_password
@@ -112,10 +112,10 @@ python -c "import secrets; print(secrets.token_urlsafe(48))"
```bash
# 1. 登录 MySQL,创建数据库
mysql -uroot -p
CREATE DATABASE `djangoadmin.tornado.elevue2` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `djangoadmin.tornado.antdvue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
# 2. 导入数据库脚本(脚本位于 document/mysql/ 目录下)
mysql -uroot -p djangoadmin.tornado.elevue2 < document/mysql/djangoadmin.tornado.elevue2.sql
mysql -uroot -p djangoadmin.tornado.antdvue < document/mysql/djangoadmin.tornado.antdvue.sql
```
:::tip 温馨提示
@@ -16,9 +16,9 @@
## 软件说明
基于 Tornado、SQLAlchemy、Pydantic、Vue3、TypeScript、Vite、ElementPlus、MySQL 等技术栈构建的企业级前后端分离后台管理系统。
基于 Tornado、SQLAlchemy、Pydantic、Vue3、TypeScript、Vite、AntDesign、MySQL 等技术栈构建的企业级前后端分离后台管理系统。
后端采用 Python 语言,基于 Tornado 异步框架、SQLAlchemy ORM、Pydantic v2 数据校验等主流技术栈;前端基于 Vue3、TypeScript、Vite 构建,采用 ElementPlus 框架,遵循 Vue 前后端分离主流设计思想。
后端采用 Python 语言,基于 Tornado 异步框架、SQLAlchemy ORM、Pydantic v2 数据校验等主流技术栈;前端基于 Vue3、TypeScript、Vite 构建,采用 AntDesign 框架,遵循 Vue 前后端分离主流设计思想。
系统采用完全前后端分离架构,前端通过 API 调用后端服务实现数据交互。内置完善的 RBAC 权限体系,权限颗粒度精细至按钮级别;支持多主题切换,兼容桌面端和平板端。系统预置了用户、角色、菜单、部门、岗位、职级、日志、字典、配置、参数、定时任务、代码生成器等基础功能模块,开箱即用。
@@ -55,31 +55,31 @@
## 软件出处
+ 软件名称:小蚂蚁云前后端分离Tornado+ElementPlus版本
+ 软件名称:小蚂蚁云前后端分离Tornado+AntDesign版本
+ 软件作者:@小蚂蚁云团队
+ 软件协议:Apache-2.0
+ 官网网址:[https://www.djangoadmin.cn](https://www.djangoadmin.cn)
+ 文档网址:[http://docs.tornado.elevue2.djangoadmin.cn](http://docs.tornado.elevue2.djangoadmin.cn)
+ 演示地址:[http://manage.tornado.elevue2.djangoadmin.cn](http://manage.tornado.elevue2.djangoadmin.cn)
+ 文档网址:[http://docs.tornado.antdvue.djangoadmin.cn](http://docs.tornado.antdvue.djangoadmin.cn)
+ 演示地址:[http://manage.tornado.antdvue.djangoadmin.cn](http://manage.tornado.antdvue.djangoadmin.cn)
## 软件版本
| 版本名称 | 说明 | 地址 |
|:---------|:-----|:-----|
| 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_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、WTForms、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_EleVue) |
| Flask+AntDesign前后端分离版 | 基于 Flask、SQLAlchemy、WTForms、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_EleVue) |
| Flask+AntDesign前后端分离版 | 基于 Flask、SQLAlchemy、WTForms、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_AntdVue) |
| Flask+NaiveUI前后端分离版 | 基于 Flask、SQLAlchemy、WTForms、Vue3、NaiveUI、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Flask_NaiveVue) |
| Tornado+Layui混编版 | 基于 Tornado、Jinja2、Layui 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_Layui) |
| Tornado+ElementPlus前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、ElementPlus、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_EleVue) |
| Tornado+AntDesign前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_EleVue) |
| Tornado+AntDesign前后端分离版 | 基于 Tornado、SQLAlchemy、Pydantic、Vue3、AntDesign、TypeScript、Vite 等技术栈,支持 MySQL、PostgreSQL、SQLite、Oracle、SQL Server 等数据库 | [Gitee地址](https://gitee.com/djangoadmin/DjangoAdmin_Fastapi_AntdVue) |
| Tornado+NaiveUI前后端分离版 | 基于 Tornado、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+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) |
@@ -11,7 +11,7 @@
## 关于我们
小蚂蚁云团队专注于企业级后台管理解决方案,产品覆盖 Tornado、Django、Flask、Tornado 多技术栈,提供 ElementPlus / AntDesign / NaiveUI 多前端版本。官网月均访问量 10 万+,用户群体以企业开发者和技术决策者为主。
小蚂蚁云团队专注于企业级后台管理解决方案,产品覆盖 Tornado、Django、Flask、Tornado 多技术栈,提供 AntDesign / AntDesign / NaiveUI 多前端版本。官网月均访问量 10 万+,用户群体以企业开发者和技术决策者为主。
## 赞助优势
@@ -19,7 +19,7 @@
```bash
# 进入项目根目录(以实际解压路径为准)
cd DjangoAdmin_Tornado_EleVue
cd DjangoAdmin_Tornado_AntdVue
```
:::tip 温馨提示
@@ -41,7 +41,7 @@ cd DjangoAdmin_Tornado_EleVue
│ ├── modules/ # 业务模块
│ ├── api/v1/ # 路由定义
│ └── extensions/ # 扩展注册(sqlalchemy/redis)
├── ui/ # 前端源码(Vue3 + Element Plus)
├── ui/ # 前端源码(Vue3 + AntDesign)
├── scripts/ # 脚本工具(数据库初始化、数据迁移等)
├── document/ # 数据库脚本
├── tests/ # 测试代码
@@ -107,7 +107,7 @@ copy .env.example .env # Windows CMD
# ============================================================
# 基础配置
# ============================================================
TORNADO_NAME=Tornado+EleVue旗舰版
TORNADO_NAME=Tornado+AntdVue旗舰版
TORNADO_VERSION=v3.0.0
TORNADO_HOST=127.0.0.1
TORNADO_PORT=8041
@@ -120,7 +120,7 @@ TORNADO_DEBUG=True
DB_DRIVER=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=djangoadmin.tornado.elevue
DB_DATABASE=djangoadmin.tornado.antdvue
DB_USERNAME=root
DB_PASSWORD=your_mysql_password # 修改为真实密码
DB_PREFIX=tornado_
@@ -161,10 +161,10 @@ python -c "import secrets; print(secrets.token_urlsafe(48))"
```bash
# 1. 登录 MySQL,创建数据库
mysql -uroot -p
CREATE DATABASE `djangoadmin.tornado.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `djangoadmin.tornado.antdvue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
# 2. 导入数据库脚本
mysql -uroot -p djangoadmin.tornado.elevue < document/djangoadmin.tornado.elevue.sql
mysql -uroot -p djangoadmin.tornado.antdvue < document/djangoadmin.tornado.antdvue.sql
```
### 方式二:Alembic 迁移(备选)
@@ -220,7 +220,7 @@ python app.py --port=8041
| |_| || |_| || (_| || | | (__ | || |_) || (_| || | | (_| |
|____/ \__,_| \__,_||_| \___||_|| .__/ \__,_||_| \__,_|
|_|
Tornado + EleVue 企业级管理框架
Tornado + AntdVue 企业级管理框架
[INFO] 服务启动成功 → http://127.0.0.1:8041
```
@@ -22,7 +22,7 @@
**MySQL**:
```sql
CREATE DATABASE `djangoadmin.tornado.elevue`
CREATE DATABASE `djangoadmin.tornado.antdvue`
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
```
@@ -30,7 +30,7 @@ CREATE DATABASE `djangoadmin.tornado.elevue`
**PostgreSQL**:
```sql
CREATE DATABASE "djangoadmin.tornado.elevue"
CREATE DATABASE "djangoadmin.tornado.antdvue"
WITH ENCODING 'UTF8'
LC_COLLATE = 'zh_CN.UTF-8'
LC_CTYPE = 'zh_CN.UTF-8';
@@ -39,7 +39,7 @@ CREATE DATABASE "djangoadmin.tornado.elevue"
**SQL Server**:
```sql
CREATE DATABASE [djangoadmin.tornado.elevue];
CREATE DATABASE [djangoadmin.tornado.antdvue];
```
### 第二步:导入数据库脚本
@@ -51,11 +51,11 @@ MySQL 数据库脚本位于 `document/` 目录下:
mysql -uroot -p
# 导入脚本
mysql -uroot -p djangoadmin.tornado.elevue < document/djangoadmin.tornado.elevue.sql
mysql -uroot -p djangoadmin.tornado.antdvue < document/djangoadmin.tornado.antdvue.sql
```
:::tip 温馨提示
`document/djangoadmin.tornado.elevue.sql` 是 MySQL 专用脚本。对于 PostgreSQL / SQL Server / SQLite / Oracle,建议使用 Alembic 方式初始化。
`document/djangoadmin.tornado.antdvue.sql` 是 MySQL 专用脚本。对于 PostgreSQL / SQL Server / SQLite / Oracle,建议使用 Alembic 方式初始化。
:::
### 第三步:导入字典种子数据
@@ -132,7 +132,7 @@ DB_DATABASE=/path/to/djangoadmin.db
DB_DRIVER=postgresql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=djangoadmin.tornado.elevue
DB_DATABASE=djangoadmin.tornado.antdvue
DB_USERNAME=postgres
DB_PASSWORD=your_password
```
@@ -166,9 +166,9 @@ services:
# ====================================================================
# 应用服务:构建并运行本项目
# ====================================================================
tornado_elevue:
image: tornado_elevue:latest # 镜像名称
container_name: tornado_elevue # 容器名称
tornado_antdvue:
image: tornado_antdvue:latest # 镜像名称
container_name: tornado_antdvue # 容器名称
build:
context: ./ # 构建上下文为项目根目录
dockerfile: Dockerfile # 使用项目根目录的 Dockerfile
@@ -270,7 +270,7 @@ volumes:
DB_DRIVER=mysql
DB_HOST=mysql # Docker 服务名称,不是 127.0.0.1
DB_PORT=3306
DB_DATABASE=djangoadmin.tornado.elevue
DB_DATABASE=djangoadmin.tornado.antdvue
DB_USERNAME=root
DB_PASSWORD=your_mysql_password # 与 MYSQL_ROOT_PASSWORD 一致
DB_PREFIX=tornado_
@@ -12,7 +12,7 @@
| 变量名 | 默认值 | 说明 |
|:----|:----|:----|
| TORNADO_NAME | Tornado+EleVue旗舰版 | 应用名称,用于界面显示和系统标识 |
| TORNADO_NAME | Tornado+AntdVue旗舰版 | 应用名称,用于界面显示和系统标识 |
| TORNADO_VERSION | v3.0.0 | 应用版本号 |
| TORNADO_HOST | 127.0.0.1 | 应用监听地址。`127.0.0.1` 仅本地访问;容器/生产建议 `0.0.0.0` |
| TORNADO_PORT | 8041 | 应用监听端口 |
@@ -37,7 +37,7 @@
| DB_DRIVER | mysql | 数据库驱动。可选:`mysql` / `postgresql` / `mssql` / `oracle` / `sqlite` |
| DB_HOST | 127.0.0.1 | 数据库地址(`sqlite` 时忽略) |
| DB_PORT | 3306 | 数据库端口(`sqlite` 时忽略) |
| DB_DATABASE | djangoadmin.tornado.elevue | 数据库名称。`sqlite` 时作为数据库文件完整路径 |
| DB_DATABASE | djangoadmin.tornado.antdvue | 数据库名称。`sqlite` 时作为数据库文件完整路径 |
| DB_USERNAME | root | 数据库账号(`sqlite` 时忽略) |
| DB_PASSWORD | (空) | 数据库密码(`sqlite` 除外) |
| DB_PREFIX | tornado_ | 数据表前缀,所有业务表名自动添加此前缀 |
@@ -131,7 +131,7 @@ python -c "import secrets; print(secrets.token_urlsafe(48))"
# ============================================================
# 基础配置
# ============================================================
TORNADO_NAME=Tornado+EleVue旗舰版
TORNADO_NAME=Tornado+AntdVue旗舰版
TORNADO_VERSION=v3.0.0
TORNADO_HOST=127.0.0.1
TORNADO_PORT=8041
@@ -145,7 +145,7 @@ TORNADO_DEMO=False
DB_DRIVER=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=djangoadmin.tornado.elevue
DB_DATABASE=djangoadmin.tornado.antdvue
DB_USERNAME=root
DB_PASSWORD=your_password
DB_PREFIX=tornado_
@@ -1,6 +1,6 @@
# 环境准备
本章节介绍在本地搭建项目所需的基础环境和软件安装。项目采用 `Python` + `Tornado` 后端、`Vue3` + `ElementPlus` 前端的分离架构,本地开发需准备以下软件。
本章节介绍在本地搭建项目所需的基础环境和软件安装。项目采用 `Python` + `Tornado` 后端、`Vue3` + `AntDesign` 前端的分离架构,本地开发需准备以下软件。
安装软件:`Python(3.12+)`、`MySQL(8.0+)`、`Redis(3.2+)`、`Node.js(22+)`、`pnpm` 等。
@@ -211,7 +211,7 @@ mysql>
```sql
-- 创建数据库(UTF-8 编码)
CREATE DATABASE `djangoadmin.tornado.elevue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE DATABASE `djangoadmin.tornado.antdvue` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
-- 查看数据库
SHOW DATABASES;
@@ -297,7 +297,7 @@ OK
## 安装 Node.js
`Node.js` 是前端项目的运行环境,要求 `22` 或更高版本。前端基于 `Vue3` + `ElementPlus` 构建。
`Node.js` 是前端项目的运行环境,要求 `22` 或更高版本。前端基于 `Vue3` + `AntDesign` 构建。
+ 下载 Node.js
@@ -83,7 +83,7 @@ mysql -h127.0.0.1 -P3306 -uroot -p
# 如果是权限问题,授权用户
mysql -uroot -p
GRANT ALL PRIVILEGES ON `djangoadmin.tornado.elevue2`.* TO 'root'@'%' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON `djangoadmin.tornado.antdvue`.* TO 'root'@'%' IDENTIFIED BY 'your_password';
FLUSH PRIVILEGES;
```
@@ -382,7 +382,7 @@ ls static/fonts/Vera.ttf
# .env 中的上传配置
UPLOAD_ALLOWED_EXTS=.jpg,.jpeg,.png,.gif,.bmp,.ico,.txt,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.mp3,.mp4,.zip,.rar,.7z
UPLOAD_MAX_SIZE_MB=10
TORNADO_FILE_URL=http://file.tornado.elevue
TORNADO_FILE_URL=http://file.tornado.antdvue
```
:::tip 温馨提示
@@ -1,6 +1,6 @@
# 前端启动
本章节介绍如何将前端项目安装依赖并启动开发服务器。前端基于 `Vue3` + `ElementPlus` + `TypeScript`+`Vite` 构建,采用组件化开发模式,支持热更新和代理配置。
本章节介绍如何将前端项目安装依赖并启动开发服务器。前端基于 `Vue3` + `AntDesign` + `TypeScript`+`Vite` 构建,采用组件化开发模式,支持热更新和代理配置。
:::tip 温馨提示
启动前端前,请确保已完成 [环境准备](/zh/volume1/environment) 章节中的 `Node.js` 和 `pnpm` 安装,并已启动后端服务(参见 [后端启动](/zh/volume1/backend))。
@@ -1,6 +1,6 @@
# 前端页面视图
前端页面视图是用户直接交互的界面。项目使用 Vue3 + ElementPlus + Vite 构建。一个完整的 CRUD 页面由 4 个文件组成:
前端页面视图是用户直接交互的界面。项目使用 Vue3 + AntDesign + Vite 构建。一个完整的 CRUD 页面由 4 个文件组成:
```
ui/src/views/system/position/
@@ -19,10 +19,10 @@ ui/src/views/system/position/
```vue
<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>
<el-card :bordered="false" class="proCard">
</a-card>
<a-card :bordered="false" class="proCard">
<BasicTable
:columns="columns"
:request="loadDataTable"
@@ -32,30 +32,30 @@ ui/src/views/system/position/
@selection-change="onSelectionChange"
>
<template #tableTitle>
<el-button type="primary" @click="handleAdd" v-perm="['sys:position:add']">
<a-button type="primary" @click="handleAdd" v-perm="['sys:position:add']">
<template #icon>
<el-icon class="el-input__icon">
<PlusOutlined />
</el-icon>
</template>
添加岗位
</el-button>
<el-button
</a-button>
<a-button
type="danger"
@click="handleDelete()"
:disabled="!selectionData.length"
v-perm="['sys:position:batchDelete']"
>
<template #icon>
<el-icon class="el-input__icon">
<Delete />
</el-icon>
</template>
删除
</el-button>
</a-button>
</template>
</BasicTable>
</el-card>
</a-card>
<editDialog
v-if="editVisible"
@@ -68,7 +68,7 @@ ui/src/views/system/position/
<script lang="ts" setup>
import { reactive, ref, h, nextTick, defineAsyncComponent } from 'vue';
import { ColProps } from 'element-plus';
import { ColProps } from 'ant-design-vue';
import { schemas } from './querySchemas';
import { useForm } from '@/components/Form/index';
import { TableAction } from '@/components/Table';
@@ -210,7 +210,7 @@ ui/src/views/system/position/
### 代码解析
**页面结构**:`PageWrapper` 包裹两个 `el-card`,上方是搜索表单,下方是数据表格。
**页面结构**:`PageWrapper` 包裹两个 `a-card`,上方是搜索表单,下方是数据表格。
**搜索表单**:通过 `useForm` 注册,Schema 从 `querySchemas.ts` 导入。`submitOnReset: true` 表示重置时自动触发查询。
@@ -230,42 +230,42 @@ ui/src/views/system/position/
```vue
<template>
<el-dialog
<a-modal
v-model="props.visible"
:title="props.positionId ? '编辑' : '新增'"
width="500"
:close-on-click-modal="false"
:before-close="dialogClose"
:mask-closable="false"
>
<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: '80px' } }">
<a-form-item
label="岗位名称"
prop="name"
:rules="{ required: true, message: '请输入岗位名称', trigger: 'blur' }"
>
<el-input class="ls-input" v-model="formData.name" placeholder="请输入岗位名称" clearable />
</el-form-item>
<el-form-item label="岗位状态" prop="status">
<el-radio-group v-model="formData.status" name="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 class="ls-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>
<span class="dialog-footer">
<el-button @click="dialogClose">取消</el-button>
<el-button :loading="subLoading" type="primary" @click="submit"> 确定 </el-button>
<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 'element-plus';
import type { FormInstance } from 'ant-design-vue';
import { getPositionDetail, positionAdd, positionUpdate } from '@/api/system/position';
import { onMounted, reactive, shallowRef } from 'vue';
import { message } from '@/utils/auth';
@@ -369,7 +369,7 @@ ui/src/views/system/position/
```typescript
import { h } from 'vue';
import { ElTag } from 'element-plus';
import { Tag } from 'ant-design-vue';
export const columns = [
{
@@ -392,7 +392,7 @@ export const columns = [
minWidth: 100,
render(record) {
return h(
ElTag,
Tag,
{
type: record.row.status == 1 ? 'success' : 'danger',
},
@@ -455,7 +455,7 @@ export const schemas: FormSchema[] = [
label: '状态',
componentProps: {
placeholder: '请选择状态',
clearable: true,
allowClear: true,
options: [
{
label: '正常',
@@ -480,9 +480,9 @@ export const schemas: FormSchema[] = [
| `field` | 字段名,与后端查询参数对应 |
| `component` | 表单组件类型:`Input` / `Select` / `DatePicker` 等 |
| `label` | 标签文本 |
| `componentProps` | 组件属性,透传给 ElementPlus 组件 |
| `componentProps` | 组件属性,透传给 AntDesign 组件 |
**Select 组件**:`options` 定义下拉选项,`clearable: true` 允许清空选择。
**Select 组件**:`options` 定义下拉选项,`allowClear: true` 允许清空选择。
## 文件关系图
@@ -295,7 +295,7 @@ def _normalize_driver(driver: str) -> str:
DB_DRIVER = _normalize_driver(os.getenv('DB_DRIVER', 'mysql'))
DB_HOST = os.getenv('DB_HOST', '127.0.0.1')
DB_PORT = int(os.getenv('DB_PORT', '3306'))
DB_DATABASE = os.getenv('DB_DATABASE', 'djangoadmin.tornado.elevue2')
DB_DATABASE = os.getenv('DB_DATABASE', 'djangoadmin.tornado.antdvue')
DB_USERNAME = os.getenv('DB_USERNAME', 'root')
DB_PASSWORD = os.getenv('DB_PASSWORD', '')
@@ -6,7 +6,7 @@
```
┌─────────────────────────────────────────────────────────────────────────┐
│ Browser (Vue3 + ElementPlus) │
│ Browser (Vue3 + AntDesign) │
│ Axios HTTP Request / Vite Dev Proxy │
└──────────────────────────────┬──────────────────────────────────────────┘
│
@@ -167,7 +167,7 @@ class PositionAddHandler(BaseHandler):
```
┌──────────────────────────────────────────────────────────────┐
│ Frontend (Vue3) │
│ ElementPlus · Pinia · Vue Router · Axios │
│ AntDesign · Pinia · Vue Router · Axios │
└──────────────────────────┬───────────────────────────────────┘
│ HTTP / JSON
┌──────────────────────────┴───────────────────────────────────┐
@@ -60,13 +60,13 @@ SQLAlchemy 是 Python 生态中最成熟、功能最完备的 ORM 框架,2.0
## 前端技术栈
### Vue3 + Vite + ElementPlus
### Vue3 + Vite + AntDesign
| 组件 | 版本 | 选型理由 |
|:---|:---|:---|
| **Vue3** | 3.x | Composition API、更好的 TypeScript 支持 |
| **Vite** | 5.x | 极速 HMR、ESM 原生支持、开箱即用的 TypeScript 支持 |
| **ElementPlus** | 2.x | Vue3 生态最成熟的 UI 组件库,企业级后台管理系统首选 |
| **AntDesign** | 2.x | Vue3 生态最成熟的 UI 组件库,企业级后台管理系统首选 |
| **Pinia** | 2.x | Vue3 官方推荐的状态管理方案,替代 Vuex |
| **Vue Router** | 4.x | Vue3 官方路由,支持动态路由、路由守卫 |
| **Axios** | 1.x | HTTP 客户端,支持请求/响应拦截器、取消请求 |
@@ -130,4 +130,4 @@ Redis 在系统中承担多个关键职责:JWT 令牌黑名单、登录失败
## 总结
项目的技术选型以「高性能、类型安全、可扩展」为核心原则。Tornado + SQLAlchemy 2.0 + Pydantic v2 构成后端铁三角,Vue3 + Vite + ElementPlus 提供现代化前端体验,Redis 承担缓存与中间件职责。各组件版本经过充分测试验证,形成稳定的技术底座。选型时优先考虑组件的独立性和可替换性,避免强耦合,为未来的架构演进预留空间。
项目的技术选型以「高性能、类型安全、可扩展」为核心原则。Tornado + SQLAlchemy 2.0 + Pydantic v2 构成后端铁三角,Vue3 + Vite + AntDesign 提供现代化前端体验,Redis 承担缓存与中间件职责。各组件版本经过充分测试验证,形成稳定的技术底座。选型时优先考虑组件的独立性和可替换性,避免强耦合,为未来的架构演进预留空间。
@@ -104,11 +104,11 @@ def get_tree(data, parent_id):
## 前端使用
分类选择通常使用 el-tree 或 el-cascader 组件:
分类选择通常使用 a-tree 或 a-cascader 组件:
```vue
<!-- 树形选择 -->
<el-tree-select
<a-tree-select
v-model="form.category_id"
:data="categoryTree"
:props="{ label: 'name', value: 'id' }"
@@ -124,6 +124,6 @@ def get_tree(data, parent_id):
2. 通用设计:适用于文章分类、产品分类等场景
3. 分类编码:category_code 字段支持程序化标识
4. 排序控制:sort 字段控制分类显示顺序
5. 前端集成:el-tree-select 或 el-cascader 组件
5. 前端集成:a-tree-select 或 a-cascader 组件
6. 继承 BaseService:声明 page_like_fields/page_eq_fields 即可获得分页能力
```
@@ -168,7 +168,7 @@ async def delete_role(role_id):
角色管理页面通常包含菜单树组件,用于勾选分配权限:
```vue
<el-tree
<a-tree
ref="menuTree"
:data="menuTreeData"
show-checkbox
@@ -140,7 +140,7 @@ PUT /api/v1/config/item/update
| 用途 | 枚举值(下拉选项) | 系统参数(配置值) |
| 值类型 | 简单值(字符串) | 可存储较长的配置内容 |
| 典型场景 | 状态、性别、类型 | SMTP配置、站点信息 |
| 前端用法 | el-select 选项 | 配置表单 |
| 前端用法 | a-select 选项 | 配置表单 |
## 总结
@@ -137,14 +137,14 @@ timeout = param_service.get_value_by_code('REQUEST_TIMEOUT', '30')
```vue
<template>
<el-select v-model="form.trainingMethod" placeholder="请选择培训方式">
<el-option
<a-select v-model:value="form.trainingMethod" placeholder="请选择培训方式">
<a-option
v-for="item in dictOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</a-select>
</template>
<script setup>
@@ -166,7 +166,7 @@ onMounted(async () => {
```typescript
import { h } from 'vue';
import { ElTag } from 'element-plus';
import { Tag } from 'ant-design-vue';
export const columns = [
{
@@ -175,7 +175,7 @@ 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] }, {
return h(Tag, { type: typeMap[record.row.trainingMethod] }, {
default: () => map[record.row.trainingMethod] || '-'
});
},
@@ -195,7 +195,7 @@ export const schemas: FormSchema[] = [
label: '培训方式',
componentProps: {
placeholder: '请选择培训方式',
clearable: true,
allowClear: true,
options: [
{ label: '线上', value: '1' },
{ label: '线下', value: '2' },
@@ -175,15 +175,15 @@ const dictStore = useDictStore()
const statusOptions = dictStore.getDict('sys_status')
// [{label: "正常", value: "1"}, {label: "停用", value: "2"}]
// 在 el-select 中使用
<el-select v-model="form.status">
<el-option
// 在 a-select 中使用
<a-select v-model:value="form.status">
<a-option
v-for="item in statusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</a-select>
```
## 常用字典示例
@@ -202,7 +202,7 @@ const statusOptions = dictStore.getDict('sys_status')
```
1. 两级结构:字典类型(分组)+ 字典项(具体值),清晰管理
2. 进程内缓存:dict_util 内置 TTL 缓存(60秒兜底),变更时主动失效
3. 前端集成:useDictStore 统一管理,el-select 直接绑定
3. 前端集成:useDictStore 统一管理,a-select 直接绑定
4. 值为字符串:字典项 value 统一为字符串,get_dict_label 自动转字符串匹配
5. 避免硬编码:枚举值统一走字典,修改无需改代码
6. 三种取值:get_dict_items(列表)、get_dict_map(映射)、get_dict_label(单值翻译)
@@ -185,10 +185,10 @@ def permission_required(permission: str):
```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>
```
### 路由守卫
@@ -136,7 +136,7 @@ class BaseSchema(BaseModel):
```
:::warning 前端空字符串
前端某些组件(如 el-select)在未选择时可能传空字符串 `""`,BaseSchema 的 `empty_str_to_none` 验证器会自动将其转为 `None`,避免整数字段解析报错。
前端某些组件(如 a-select)在未选择时可能传空字符串 `""`,BaseSchema 的 `empty_str_to_none` 验证器会自动将其转为 `None`,避免整数字段解析报错。
:::
## 唯一性校验
@@ -208,13 +208,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('登录身份已失效,请重新登录!', '提示', { ... })
messageBox.confirm('登录身份已失效,请重新登录!', '提示', { ... })
.then(() => {
storage.clear();
window.location.href = '/login';
@@ -304,7 +304,7 @@ 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
@@ -312,12 +312,12 @@ responseInterceptorsCatch: (error: any) => {
const { response, code } = error || {};
// 超时
if (code === 'ECONNABORTED') {
ElMessage.error('接口请求超时,请刷新页面重试!');
message.error('接口请求超时,请刷新页面重试!');
return;
}
// 网络异常
if (error.toString().includes('Network Error')) {
ElMessageBox.confirm('请检查您的网络连接是否正常', '网络异常', { ... });
messageBox.confirm('请检查您的网络连接是否正常', '网络异常', { ... });
return Promise.reject(error);
}
// 其他 HTTP 状态码
@@ -66,8 +66,8 @@ Vite 按 mode 加载环境变量,后面的文件覆盖前面的:
|------|------|--------|------|
| `VITE_PORT` | `number` | `8001` | 开发服务器端口 |
| `VITE_PUBLIC_PATH` | `string` | `/` | 部署基础路径(子目录部署时修改) |
| `VITE_GLOB_APP_TITLE` | `string` | `Tornado+ElementPlus版` | 应用标题(注入 HTML) |
| `VITE_GLOB_APP_SHORT_NAME` | `string` | `Tornado+ElementPlus版` | 应用短名(用于生成运行时配置变量名) |
| `VITE_GLOB_APP_TITLE` | `string` | `Tornado+AntDesign版` | 应用标题(注入 HTML) |
| `VITE_GLOB_APP_SHORT_NAME` | `string` | `Tornado+AntDesign版` | 应用短名(用于生成运行时配置变量名) |
| `VITE_GLOB_API_URL` | `string` | 空 | API 基础路径(留空使用相对路径) |
| `VITE_GLOB_API_URL_PREFIX` | `string` | `/api` | API 前缀 |
| `VITE_GLOB_FILE_URL` | `string` | - | 文件访问域名(拼接上传文件 URL) |
@@ -175,7 +175,7 @@ import { defineStore } from 'pinia'; // 自动
```html
<!-- 构建后的 index.html -->
<title>Tornado+ElementPlus版</title>
<title>Tornado+AntDesign版</title>
<script src="/app.config.js?v=1.0.0-1725000000000"></script>
```
@@ -185,14 +185,14 @@ import { defineStore } from 'pinia'; // 自动
```javascript
// dist/app.config.js(构建后自动生成,不可手动修改)
window.__PRODUCTION_TORNADO_ELEMENTPLUS___CONF__ = {
window.__PRODUCTION_TORNADO_ANTDVue___CONF__ = {
"VITE_GLOB_API_URL": "",
"VITE_GLOB_API_URL_PREFIX": "/api",
"VITE_GLOB_FILE_URL": "http://file.tornado.elevue2",
"VITE_GLOB_APP_TITLE": "Tornado+ElementPlus版",
"VITE_GLOB_FILE_URL": "http://file.tornado.antdvue",
"VITE_GLOB_APP_TITLE": "Tornado+AntDesign版",
// ...
};
Object.freeze(window.__PRODUCTION_TORNADO_ELEMENTPLUS___CONF__);
Object.freeze(window.__PRODUCTION_TORNADO_ANTDVue___CONF__);
```
:::warning 为什么需要运行时配置?
@@ -249,7 +249,7 @@ dist/
├── app.config.js # 运行时配置(VITE_GLOB_* 变量)
├── favicon.ico # 网站图标
└── assets/
├── index-[hash].js # 主包(Vue、ElementPlus、Pinia 等)
├── index-[hash].js # 主包(Vue、AntDesign、Pinia 等)
├── index-[hash].css # 全局样式
├── [name]-[hash].js # 路由懒加载 chunk(按视图分割)
└── ... # 字体、图片等静态资源
@@ -276,7 +276,7 @@ Vite 自动将 `node_modules` 中的库分割为独立 chunk:
| chunk | 内容 |
|-------|------|
| `vendor-vue.js` | vue + vue-router + pinia |
| `vendor-element.js` | element-plus |
| `vendor-element.js` | ant-design-vue |
| `vendor-echarts.js` | echarts(如果使用) |
| `vendor-*.js` | 其他大型依赖 |
@@ -286,7 +286,7 @@ Vite 自动将 `node_modules` 中的库分割为独立 chunk:
```typescript
optimizeDeps: {
include: ['dayjs', '@vicons/ionicons5', '@vicons/antd', '@element-plus/icons-vue'],
include: ['dayjs', '@vicons/ionicons5', '@vicons/antd', '@ant-design-vue/icons-vue'],
}
```
@@ -300,13 +300,13 @@ optimizeDeps: {
async function bootstrap() {
const app = createApp(App);
// 1. 全局注册 ElementPlus 图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
// 1. 全局注册 AntDesign 图标
for (const [key, component] of Object.entries(AntDesignIconsVue)) {
app.component(key, component);
}
// 2. 全局引入 ElementPlus(中文包)
setupElement(app);
// 2. 全局引入 AntDesign(中文包)
setupAntd(app);
// 3. 全局注册自定义组件(BasicTable、BasicForm 等)
setupCustomComponents(app);
@@ -332,7 +332,7 @@ async function bootstrap() {
| 注册项 | 文件 | 说明 |
|--------|------|------|
| ElementPlus | `element.ts` | 全局引入 + 中文包 |
| AntDesign | `element.ts` | 全局引入 + 中文包 |
| 自定义组件 | `customComponents.ts` | BasicTable、BasicForm、PageWrapper 等 |
| 自定义指令 | `directives.ts` | `v-perm`、`v-perms`、`v-scrollBar` |
@@ -497,7 +497,7 @@ pnpm report
**优化手段**:
1. 路由懒加载(已默认启用)
2. 按需引入 ElementPlus 组件(当前为全量引入)
2. 按需引入 AntDesign 组件(当前为全量引入)
3. 大型依赖(echarts、wangeditor)按需引入
4. 开启 gzip 压缩(`VITE_BUILD_COMPRESS=gzip`)
@@ -1,7 +1,7 @@
# 公共组件库
:::tip 说明
项目提供丰富的公共组件,位于 `ui/src/components/` 目录,基于 ElementPlus 二次封装,覆盖表格、表单、弹窗、上传、富文本编辑等常见场景。组件通过 `src/plugins/` 中的 `setupCustomComponents` 全局注册,任何页面直接使用,无需单独 import。
项目提供丰富的公共组件,位于 `ui/src/components/` 目录,基于 AntDesign 二次封装,覆盖表格、表单、弹窗、上传、富文本编辑等常见场景。组件通过 `src/plugins/` 中的 `setupCustomComponents` 全局注册,任何页面直接使用,无需单独 import。
:::
## 组件总览
@@ -17,7 +17,7 @@
| CropperImage | `components/Cropper/` | 图片裁剪(圆形/矩形、实时预览) |
| QrCode | `components/Qrcode/` | 二维码生成(canvas/img、内嵌 logo) |
| ChinaArea | `components/ChinaArea/` | 省市区级联(懒加载、二/三级联动) |
| Icon | `components/icon/` | 图标(支持 ElementPlus 图标 + SVG 图标) |
| Icon | `components/icon/` | 图标(支持 AntDesign 图标 + SVG 图标) |
| BasicSelect | `components/Select/` | 增强下拉(远程请求、本地缓存) |
| CountTo | `components/CountTo/` | 数字动画(前缀/后缀、千分位、缓动曲线) |
| Password | `components/Password/` | 密码强度(强度指示器、重复确认、复杂度校验) |
@@ -38,7 +38,7 @@
## BasicTable 表格
表格组件是使用频率最高的组件,基于 `el-table` + `el-pagination` 封装,详见 [BasicTable 表格](/zh/volume4/frontend/table) 章节。
表格组件是使用频率最高的组件,基于 `a-table` + `a-pagination` 封装,详见 [BasicTable 表格](/zh/volume4/frontend/table) 章节。
### Props
@@ -129,9 +129,9 @@
<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-button v-perm="['sys:level:add']" type="primary" @click="handleAdd">
<Plus />新增
</a-button>
</template>
</BasicTable>
</template>
@@ -299,7 +299,7 @@ const [registerForm] = useForm({
## BasicModal 弹窗
弹窗组件基于 `el-dialog` 封装,支持拖拽、自定义按钮,详见 [Modal 弹窗](/zh/volume4/frontend/modal) 章节。
弹窗组件基于 `a-modal` 封装,支持拖拽、自定义按钮,详见 [Modal 弹窗](/zh/volume4/frontend/modal) 章节。
### Props
@@ -356,7 +356,7 @@ function handleEdit(record) {
## BasicUpload 上传
文件/图片上传组件,基于 `el-upload` 封装,支持卡片/列表模式、数量限制、图片预览。
文件/图片上传组件,基于 `a-upload` 封装,支持卡片/列表模式、数量限制、图片预览。
### Props
@@ -698,10 +698,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>
@@ -724,7 +724,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>
```
@@ -732,7 +732,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>
```
:::
@@ -740,7 +740,7 @@ async function getDeptOptions() {
## Icon 图标
统一图标组件,支持 ElementPlus 图标和 SVG 图标。
统一图标组件,支持 AntDesign 图标和 SVG 图标。
### Props
@@ -754,8 +754,8 @@ async function getDeptOptions() {
```vue
<template>
<!-- ElementPlus 图标 -->
<Icon name="el-icon-Plus" size="18px" />
<!-- AntDesign 图标 -->
<Icon name="PlusOutlined" size="18px" />
<!-- SVG 图标 -->
<Icon name="svg-icon-user" />
</template>
@@ -836,4 +836,4 @@ setupCustomComponents(app); // 注册所有自定义组件
## 总结
公共组件库基于 ElementPlus 二次封装,核心三件套 BasicTable(表格)、BasicForm(表单)、BasicModal(弹窗)覆盖列表页标准场景。辅以 Upload、Editor、Cropper 等业务组件,以及 Authority、v-perm 权限体系。通过全局注册,任何页面直接使用,无需单独 import。优先使用公共组件,避免重复开发。
公共组件库基于 AntDesign 二次封装,核心三件套 BasicTable(表格)、BasicForm(表单)、BasicModal(弹窗)覆盖列表页标准场景。辅以 Upload、Editor、Cropper 等业务组件,以及 Authority、v-perm 权限体系。通过全局注册,任何页面直接使用,无需单独 import。优先使用公共组件,避免重复开发。
@@ -1,7 +1,7 @@
# BasicForm 表单
:::tip 说明
`BasicForm` 位于 `src/components/Form/src/BasicForm.vue`,基于 ElementPlus 的 `el-form` 二次封装,通过 schemas 配置化定义表单字段,支持查询表单和业务表单两种模式。
`BasicForm` 位于 `src/components/Form/src/BasicForm.vue`,基于 AntDesign 的 `a-form` 二次封装,通过 schemas 配置化定义表单字段,支持查询表单和业务表单两种模式。
:::
## 组件架构
@@ -24,9 +24,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 定义
@@ -51,7 +51,7 @@ export const schemas: FormSchema[] = [
label: '状态',
componentProps: {
placeholder: '请选择状态',
clearable: true,
allowClear: true,
options: [
{ label: '正常', value: '1' },
{ label: '禁用', value: '2' },
@@ -66,7 +66,7 @@ export const schemas: FormSchema[] = [
```typescript
import { schemas } from './querySchemas';
import { useForm } from '@/components/Form/index';
import { ColProps } from 'element-plus';
import { ColProps } from 'ant-design-vue';
const formParams = reactive({ name: '', status: '' });
@@ -93,73 +93,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="500">
<a-form class="ls-form" ref="formRef" :model="formData" :label-col="{ style: { width: '80px' } }">
<a-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="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">
<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` 动态渲染对应的 AntDesign 组件:
```vue
<!-- src/components/Form/src/BasicForm.vue 核心逻辑 -->
<template v-for="schema in getSchema" :key="schema.field">
<!-- 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">
<a-checkbox-group v-model:value="formModel[schema.field]">
<a-checkbox v-for="item in schema.componentProps?.options" :value="item.value">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</a-checkbox>
</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">
<a-radio-group v-model:value="formModel[schema.field]">
<a-radio v-for="item in schema.componentProps?.options" :value="item.value">
{{ item.label }}
</el-radio>
</el-radio-group>
</a-radio>
</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>
<a-select v-model:value="formModel[schema.field]">
<a-option v-for="item in schema.componentProps?.options" :value="item.value" :label="item.label" />
</a-select>
</template>
<!-- Daterange -->
<template v-else-if="schema.component === 'Daterange'">
<el-date-picker v-model="formModel[schema.field]" type="daterange" value-format="YYYY-MM-DD" />
<a-range-picker v-model:value="formModel[schema.field]" 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 :is="`a-${schema.component}`" v-model="formModel[schema.field]" />
</template>
```
@@ -237,4 +237,4 @@ onMounted(() => {
## 总结
BasicForm 通过 schemas 配置化定义表单字段,内部根据 `component` 动态渲染对应的 ElementPlus 组件。查询表单使用 `useForm` 注册并绑定 `@submit`/`@reset` 事件,业务表单直接使用 `el-form` + `reactive` 管理数据。支持多种组件类型、表单校验、数据绑定、条件隐藏等能力。
BasicForm 通过 schemas 配置化定义表单字段,内部根据 `component` 动态渲染对应的 AntDesign 组件。查询表单使用 `useForm` 注册并绑定 `@submit`/`@reset` 事件,业务表单直接使用 `a-form` + `reactive` 管理数据。支持多种组件类型、表单校验、数据绑定、条件隐藏等能力。
@@ -130,7 +130,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
@@ -1,22 +1,22 @@
# 国际化
:::tip 说明
当前项目仅支持中文界面,ElementPlus 使用中文语言包(`zh-cn`),所有界面文本直接硬编码在组件中。如需扩展多语言支持,可基于 vue-i18n 实现。
当前项目仅支持中文界面,AntDesign 使用中文语言包(`zh-cn`),所有界面文本直接硬编码在组件中。如需扩展多语言支持,可基于 vue-i18n 实现。
:::
## 当前状态
项目当前为中文单语言版本:
- ElementPlus 全局配置中文语言包:`app.use(ElementPlus, { locale: zhCn })`
- 所有界面文本直接硬编码在组件中:`<el-button>添加岗位</el-button>`
- AntDesign 全局配置中文语言包:`app.use(AntDesign, { locale: zhCn })`
- 所有界面文本直接硬编码在组件中:`<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>
<a-table-column label="岗位名称" prop="name" />
<a-table-column label="创建时间" prop="createTime" />
</template>
```
@@ -65,8 +65,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>
<a-table-column :label="$t('position.name')" prop="name" />
</template>
<script setup>
@@ -75,13 +75,13 @@ import { useI18n } from 'vue-i18n';
</script>
```
### ElementPlus 国际化
### AntDesign 国际化
```typescript
import zhCn from 'element-plus/es/locale/lang/zh-cn';
import en from 'element-plus/es/locale/lang/en';
import zhCn from 'ant-design-vue/es/locale/lang/zh-cn';
import en from 'ant-design-vue/es/locale/lang/en';
app.use(ElementPlus, { locale: zhCn });
app.use(AntDesign, { locale: zhCn });
```
### 语言切换组件
@@ -89,19 +89,19 @@ app.use(ElementPlus, { locale: zhCn });
```vue
<!-- src/components/LangSelect/index.vue -->
<template>
<el-dropdown @command="handleLangChange">
<a-dropdown @command="handleLangChange">
<span class="lang-icon">🌐</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="zh-CN" :disabled="locale === 'zh-CN'">
<a-dropdown-menu>
<a-dropdown-item command="zh-CN" :disabled="locale === 'zh-CN'">
中文
</el-dropdown-item>
<el-dropdown-item command="en-US" :disabled="locale === 'en-US'">
</a-dropdown-item>
<a-dropdown-item command="en-US" :disabled="locale === 'en-US'">
English
</el-dropdown-item>
</el-dropdown-menu>
</a-dropdown-item>
</a-dropdown-menu>
</template>
</el-dropdown>
</a-dropdown>
</template>
<script setup lang="ts">
@@ -115,7 +115,7 @@ const handleLangChange = (lang: string) => {
locale.value = lang;
localStorage.setItem('lang', lang);
appStore.setLocale(lang);
// 刷新页面以更新 ElementPlus 语言包
// 刷新页面以更新 AntDesign 语言包
window.location.reload();
};
</script>
@@ -137,7 +137,7 @@ const msgMap: Record<number, string> = {
// Axios 响应拦截器中
if (response.data.code !== 0) {
const msg = msgMap[response.data.code] || response.data.msg;
ElMessage.error(msg);
message.error(msg);
}
```
@@ -147,4 +147,4 @@ if (response.data.code !== 0) {
## 总结
当前项目为中文单语言版本,ElementPlus 使用中文语言包。如需扩展多语言支持,可基于 vue-i18n 实现前端国际化,配合 ElementPlus 内置语言包和后端消息 code 实现全链路多语言。语言切换组件可放在顶栏,支持用户手动切换。
当前项目为中文单语言版本,AntDesign 使用中文语言包。如需扩展多语言支持,可基于 vue-i18n 实现前端国际化,配合 AntDesign 内置语言包和后端消息 code 实现全链路多语言。语言切换组件可放在顶栏,支持用户手动切换。
@@ -1,7 +1,7 @@
# Modal 弹窗
:::tip 说明
项目提供 `BasicModal` 组件(`src/components/Modal/src/BasicModal.vue`)和原生 `el-dialog` 两种弹窗用法。编辑弹窗通过 `defineAsyncComponent` 懒加载,减少首屏渲染负担。
项目提供 `BasicModal` 组件(`src/components/Modal/src/BasicModal.vue`)和原生 `a-modal` 两种弹窗用法。编辑弹窗通过 `defineAsyncComponent` 懒加载,减少首屏渲染负担。
:::
## 组件架构
@@ -9,7 +9,7 @@
```
src/components/Modal/
├── src/
│ ├── BasicModal.vue # 主组件(基于 el-dialog)
│ ├── BasicModal.vue # 主组件(基于 a-modal)
│ ├── props.ts # 属性定义
│ ├── type.ts # 类型定义(ModalProps、ModalMethods)
│ └── hooks/
@@ -19,7 +19,7 @@ src/components/Modal/
## BasicModal 组件
`BasicModal` 基于 `el-dialog` 封装,提供全屏切换、自定义头部、提交按钮等能力:
`BasicModal` 基于 `a-modal` 封装,提供全屏切换、自定义头部、提交按钮等能力:
```vue
<BasicModal :title="'编辑'" :width="600" @register="registerModal" @ok="handleSubmit">
@@ -36,7 +36,7 @@ src/components/Modal/
## 实际用法(position 模块)
以岗位管理为例,实际使用原生 `el-dialog` + `defineAsyncComponent` 懒加载:
以岗位管理为例,实际使用原生 `a-modal` + `defineAsyncComponent` 懒加载:
### 父组件(index.vue)
@@ -81,36 +81,36 @@ const handleEdit = async (record: Recordable) => {
```vue
<!-- src/views/system/position/edit.vue -->
<template>
<el-dialog
<a-modal
v-model="props.visible"
:title="props.positionId ? '编辑' : '新增'"
width="500"
:close-on-click-modal="false"
:before-close="dialogClose"
:mask-closable="false"
>
<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: '80px' } }">
<a-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="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">
<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>
<a-button @click="dialogClose">取消</a-button>
<a-button :loading="subLoading" type="primary" @click="submit">确定</a-button>
</template>
</el-dialog>
</a-modal>
</template>
<script lang="ts" setup>
@@ -190,7 +190,7 @@ async function handleDelete(record: Recordable) {
}
```
`confirm` 内部调用 `ElMessageBox.confirm`,返回 Promise,用户取消时抛出异常中断后续逻辑。
`confirm` 内部调用 `messageBox.confirm`,返回 Promise,用户取消时抛出异常中断后续逻辑。
## v-model:visible 双向绑定
@@ -213,4 +213,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')` 通知父组件刷新表格。
@@ -90,15 +90,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 结合
@@ -113,7 +113,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,15 +125,15 @@ const showAddBtn = computed(() => hasPermission(['sys:position:add']));
<template>
<BasicTable :columns="columns" :request="loadDataTable" ref="tableRef" :actionColumn="actionColumn">
<template #tableTitle>
<el-button type="primary" @click="handleAdd" v-perm="['sys:position:add']">
<template #icon><el-icon><PlusOutlined /></el-icon></template>
<a-button type="primary" @click="handleAdd" v-perm="['sys:position:add']">
<template #icon><PlusOutlined /></template>
添加岗位
</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']">
<template #icon><el-icon><Delete /></el-icon></template>
<template #icon><Delete /></template>
删除
</el-button>
</a-button>
</template>
</BasicTable>
</template>
@@ -202,7 +202,7 @@ class PositionAddHandler(BaseHandler):
```vue
<!-- 前端 -->
<el-button v-perm="['sys:position:add']">添加岗位</el-button>
<a-button v-perm="['sys:position:add']">添加岗位</a-button>
```
## 超级管理员
@@ -1,7 +1,7 @@
# 前端环境与结构
:::tip 说明
前端基于 Vue3 + ElementPlus 构建,使用 pnpm 作为包管理工具,Vite 作为构建工具,TypeScript 作为开发语言。
前端基于 Vue3 + AntDesign 构建,使用 pnpm 作为包管理工具,Vite 作为构建工具,TypeScript 作为开发语言。
:::
## 技术栈
@@ -9,7 +9,7 @@
| 技术 | 版本 | 说明 |
|------|------|------|
| Vue | 3.x | 渐进式 JavaScript 框架,组合式 API |
| ElementPlus | 2.x | Vue3 组件库 |
| AntDesign | 2.x | Vue3 组件库 |
| Vite | 3.x | 下一代前端构建工具 |
| Pinia | 2.x | 状态管理(Vuex 替代方案) |
| Vue Router | 4.x | 路由管理 |
@@ -140,24 +140,24 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
```typescript
import './styles/tailwindbase.css';
import 'element-plus/dist/index.css';
import 'ant-design-vue/dist/index.css';
import './styles/index.scss';
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
import * as AntDesignIconsVue from '@ant-design-vue/icons-vue';
import { createApp } from 'vue';
import App from './App.vue';
import router, { setupRouter } from './router';
import { setupStore } from '@/store';
import { setupElement, setupDirectives, setupCustomComponents } from '@/plugins';
import { setupAntd, setupDirectives, setupCustomComponents } from '@/plugins';
async function bootstrap() {
const app = createApp(App);
// 注册所有 ElementPlus 图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
// 注册所有 AntDesign 图标
for (const [key, component] of Object.entries(AntDesignIconsVue)) {
app.component(key, component);
}
setupElement(app); // 全局引入 ElementPlus 组件
setupAntd(app); // 全局引入 AntDesign 组件
setupCustomComponents(app); // 注册自定义组件
setupDirectives(app); // 注册自定义指令(v-perm 等)
setupStore(app); // 挂载 Pinia 状态管理
@@ -170,7 +170,7 @@ async function bootstrap() {
void bootstrap();
```
初始化顺序:样式加载 -> ElementPlus -> 自定义组件 -> 自定义指令 -> Pinia Store -> Router -> 挂载。
初始化顺序:样式加载 -> AntDesign -> 自定义组件 -> 自定义指令 -> Pinia Store -> Router -> 挂载。
## 核心目录说明
@@ -180,7 +180,7 @@ API 接口定义目录,按模块组织文件。公共接口在 `api/common/`
### components/
公共组件目录,基于 ElementPlus 二次封装,包含 `Table`(BasicTable 表格)、`Form`(BasicForm 表单)、`Modal`(BasicModal 弹窗)、`Upload`、`Editor`、`Cropper`、`Select` 等。
公共组件目录,基于 AntDesign 二次封装,包含 `Table`(BasicTable 表格)、`Form`(BasicForm 表单)、`Modal`(BasicModal 弹窗)、`Upload`、`Editor`、`Cropper`、`Select` 等。
### directives/
@@ -212,4 +212,4 @@ Pinia 状态管理目录,核心模块:
## 总结
前端项目基于 Vue3 + ElementPlus + Vite + TypeScript + Pinia 构建,使用 pnpm 管理依赖,Tailwind CSS + SCSS 提供样式能力。通过 `main.ts` 的 `bootstrap()` 函数按顺序初始化所有基础设施,目录结构按功能分层组织,便于维护和扩展。
前端项目基于 Vue3 + AntDesign + Vite + TypeScript + Pinia 构建,使用 pnpm 管理依赖,Tailwind CSS + SCSS 提供样式能力。通过 `main.ts` 的 `bootstrap()` 函数按顺序初始化所有基础设施,目录结构按功能分层组织,便于维护和扩展。
@@ -1,7 +1,7 @@
# 样式与主题
:::tip 说明
前端样式方案采用 Tailwind CSS + SCSS + ElementPlus 主题覆盖,支持深色模式切换。全局 SCSS 变量通过 Vite 的 `additionalData` 注入。
前端样式方案采用 Tailwind CSS + SCSS + AntDesign 主题覆盖,支持深色模式切换。全局 SCSS 变量通过 Vite 的 `additionalData` 注入。
:::
## 样式架构
@@ -11,7 +11,7 @@ src/styles/
├── var.scss # SCSS 全局变量
├── index.scss # 入口文件,引入所有样式
├── tailwindbase.css # Tailwind CSS 基础样式
└── element-override.css # ElementPlus 主题覆盖(可选)
└── element-override.css # AntDesign 主题覆盖(可选)
```
## SCSS 全局变量
@@ -56,36 +56,36 @@ import './styles/tailwindbase.css';
<div class="w-full md:w-1/2 lg:w-1/3">响应式宽度</div>
```
## ElementPlus 主题
## AntDesign 主题
ElementPlus 通过 CSS 变量支持主题定制:
AntDesign 通过 CSS 变量支持主题定制:
```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 变量
import 'ant-design-vue/dist/index.css';
import 'ant-design-vue/theme-chalk/display.css';
import 'ant-design-vue/theme-chalk/dark/css-vars.css'; // 深色模式 CSS 变量
```
### ElementPlus 全局引入
### AntDesign 全局引入
```typescript
// src/plugins/setupElement.ts
import ElementPlus from 'element-plus';
import zhCn from 'element-plus/es/locale/lang/zh-cn';
// src/plugins/setupAntd.ts
import AntDesign from 'ant-design-vue';
import zhCn from 'ant-design-vue/es/locale/lang/zh-cn';
export function setupElement(app) {
app.use(ElementPlus, { locale: zhCn, size: 'default' });
export function setupAntd(app) {
app.use(AntDesign, { locale: zhCn, size: 'default' });
}
```
## 深色模式
通过 ElementPlus 的 `dark/css-vars.css` 和 Tailwind 的 `dark` 模式实现深色主题:
通过 AntDesign 的 `dark/css-vars.css` 和 Tailwind 的 `dark` 模式实现深色主题:
```vue
<template>
<el-switch v-model="isDark" @change="toggleDark" />
<a-switch v-model:checked="isDark" @change="toggleDark" />
</template>
<script setup>
@@ -100,9 +100,9 @@ const toggleDark = (val) => {
```css
.dark {
--el-bg-color: #1d1e1f;
--el-text-color-primary: #e5eaf3;
--el-border-color: #4c4d4f;
--ant-bg-color: #1d1e1f;
--ant-text-color: #e5eaf3;
--ant-border-color: #4c4d4f;
}
```
@@ -130,12 +130,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>
```
@@ -150,4 +150,4 @@ export function useDesignSetting() {
## 总结
样式方案采用 Tailwind CSS 提供原子化样式 + SCSS 全局变量(Vite `additionalData` 注入)+ ElementPlus 主题覆盖,支持深色模式切换。`designSetting` store 管理主题配置,`PageWrapper` 组件提供统一页面容器。
样式方案采用 Tailwind CSS 提供原子化样式 + SCSS 全局变量(Vite `additionalData` 注入)+ AntDesign 主题覆盖,支持深色模式切换。`designSetting` store 管理主题配置,`PageWrapper` 组件提供统一页面容器。
@@ -1,7 +1,7 @@
# BasicTable 表格
:::tip 说明
`BasicTable` 位于 `src/components/Table/src/Table.vue`,基于 ElementPlus 的 `el-table` 二次封装,提供列定义、数据请求、分页、操作列、多选、全屏、密度调整、列设置等能力。
`BasicTable` 位于 `src/components/Table/src/Table.vue`,基于 AntDesign 的 `a-table` 二次封装,提供列定义、数据请求、分页、操作列、多选、全屏、密度调整、列设置等能力。
:::
## 组件架构
@@ -38,7 +38,7 @@ src/components/Table/
@selection-change="onSelectionChange"
>
<template #tableTitle>
<el-button type="primary" @click="handleAdd" v-perm="['sys:position:add']">添加岗位</el-button>
<a-button type="primary" @click="handleAdd" v-perm="['sys:position:add']">添加岗位</a-button>
</template>
</BasicTable>
```
@@ -51,7 +51,7 @@ src/components/Table/
// 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' },
@@ -63,7 +63,7 @@ export const columns = [
minWidth: 100,
render(record) {
return h(
ElTag,
Tag,
{ type: record.row.status == 1 ? 'success' : 'danger' },
{ default: () => (record.row.status == 1 ? '正常' : '停用') },
);
@@ -23,7 +23,7 @@ export function getNormalPath(path: string) {
// 确认弹窗封装
export function confirm(msg: string, type: 'warning') {
return ElMessageBox.confirm(msg, '温馨提示', {
return messageBox.confirm(msg, '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: type,
@@ -32,7 +32,7 @@ export function confirm(msg: string, type: 'warning') {
// 消息提示封装
export function message(msg: string, type = 'success') {
ElMessage[type](msg);
message[type](msg);
}
// 文件流下载
@@ -233,9 +233,9 @@ export enum PageEnum {
```
:::tip 温馨提示
`Storage` 工具类自动将键名转为大写(如 `access-token` → `ACCESS-TOKEN`),存储时包含过期时间,读取时自动检查是否过期。`auth.ts` 中的 `confirm` 和 `message` 函数是对 ElementPlus 的简封装,在业务组件中直接调用即可。
`Storage` 工具类自动将键名转为大写(如 `access-token` → `ACCESS-TOKEN`),存储时包含过期时间,读取时自动检查是否过期。`auth.ts` 中的 `confirm` 和 `message` 函数是对 AntDesign 的简封装,在业务组件中直接调用即可。
:::
## 总结
工具函数库提供了 Storage(本地存储,支持过期)、auth(认证管理、树结构操作、文件下载)、dateUtil(日期格式化)、httpEnum/pageEnum(枚举定义)等通用能力。`confirm` 和 `message` 是 ElementPlus 的简封装,`arrayToTree`/`treeToArray` 处理菜单数据转换。
工具函数库提供了 Storage(本地存储,支持过期)、auth(认证管理、树结构操作、文件下载)、dateUtil(日期格式化)、httpEnum/pageEnum(枚举定义)等通用能力。`confirm` 和 `message` 是 AntDesign 的简封装,`arrayToTree`/`treeToArray` 处理菜单数据转换。
@@ -32,7 +32,7 @@ src/views/
| 文件 | 职责 | 说明 |
|------|------|------|
| `index.vue` | 列表页 | 搜索表单 + 工具栏 + BasicTable + 编辑弹窗引用 |
| `edit.vue` | 编辑弹窗 | el-dialog + el-form,懒加载 |
| `edit.vue` | 编辑弹窗 | a-modal + a-form,懒加载 |
| `columns.ts` | 表格列定义 | 定义列 prop、label、render 函数等 |
| `querySchemas.ts` | 搜索表单定义 | FormSchema 数组,定义搜索字段 |
@@ -44,10 +44,10 @@ src/views/
<!-- src/views/system/position/index.vue -->
<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>
<el-card :bordered="false" class="proCard">
</a-card>
<a-card :bordered="false" class="proCard">
<BasicTable
:columns="columns"
:request="loadDataTable"
@@ -57,30 +57,30 @@ src/views/
@selection-change="onSelectionChange"
>
<template #tableTitle>
<el-button type="primary" @click="handleAdd" v-perm="['sys:position:add']">
<a-button type="primary" @click="handleAdd" v-perm="['sys:position:add']">
<template #icon>
<el-icon class="el-input__icon">
<PlusOutlined />
</el-icon>
</template>
添加岗位
</el-button>
<el-button
</a-button>
<a-button
type="danger"
@click="handleDelete()"
:disabled="!selectionData.length"
v-perm="['sys:position:batchDelete']"
>
<template #icon>
<el-icon class="el-input__icon">
<Delete />
</el-icon>
</template>
删除
</el-button>
</a-button>
</template>
</BasicTable>
</el-card>
</a-card>
<editDialog
v-if="editVisible"
@@ -93,7 +93,7 @@ src/views/
<script lang="ts" setup>
import { reactive, ref, h, nextTick, defineAsyncComponent } from 'vue';
import { ColProps } from 'element-plus';
import { ColProps } from 'ant-design-vue';
import { schemas } from './querySchemas';
import { useForm } from '@/components/Form/index';
import { TableAction } from '@/components/Table';
@@ -245,41 +245,41 @@ src/views/
```vue
<!-- src/views/system/position/edit.vue -->
<template>
<el-dialog
<a-modal
v-model="props.visible"
:title="props.positionId ? '编辑' : '新增'"
width="500"
:close-on-click-modal="false"
:before-close="dialogClose"
:mask-closable="false"
>
<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: '80px' } }">
<a-form-item
label="岗位名称"
prop="name"
:rules="{ required: true, message: '请输入岗位名称', trigger: 'blur' }"
>
<el-input class="ls-input" v-model="formData.name" placeholder="请输入岗位名称" clearable />
</el-form-item>
<el-form-item label="岗位状态" prop="status">
<el-radio-group v-model="formData.status" name="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 class="ls-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>
<span class="dialog-footer">
<el-button @click="dialogClose">取消</el-button>
<el-button :loading="subLoading" type="primary" @click="submit"> 确定 </el-button>
<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 'element-plus';
import type { FormInstance } from 'ant-design-vue';
import { getPositionDetail, positionAdd, positionUpdate } from '@/api/system/position';
import { onMounted, reactive, shallowRef } from 'vue';
import { message } from '@/utils/auth';
@@ -375,7 +375,7 @@ src/views/
```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 = [
{
@@ -398,7 +398,7 @@ export const columns = [
minWidth: 100,
render(record) {
return h(
ElTag,
Tag,
{
type: record.row.status == 1 ? 'success' : 'danger',
},
@@ -446,7 +446,7 @@ export const schemas: FormSchema[] = [
label: '状态',
componentProps: {
placeholder: '请选择状态',
clearable: true,
allowClear: true,
options: [
{
label: '正常',
@@ -94,13 +94,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('登录身份已失效,请重新登录!', '提示', {
messageBox.confirm('登录身份已失效,请重新登录!', '提示', {
confirmButtonText: '确定',
showCancelButton: false,
type: 'warning',
@@ -140,14 +140,14 @@ const result = await getPositionList(params);
| code | 说明 | 前端行为 |
|------|------|----------|
| 0 | 成功 | 返回 `data` |
| 1 | 业务失败 | `ElMessage.error(msg)` + `throw` |
| 1 | 业务失败 | `message.error(msg)` + `throw` |
| 401 | 未登录或 Token 过期 | 弹出确认框 → 跳转登录页 |
HTTP 状态码始终为 200,业务成功/失败通过 `code` 字段区分。
## 前端错误展示
业务错误通过 `ElMessage.error` 弹出提示,无需组件手动处理:
业务错误通过 `message.error` 弹出提示,无需组件手动处理:
```typescript
// 组件中直接调用,错误由拦截器处理
@@ -155,7 +155,7 @@ try {
await positionAdd(formData);
message('操作成功');
} catch (e) {
// 拦截器已弹出 ElMessage.error,此处可选处理
// 拦截器已弹出 message.error,此处可选处理
}
```
@@ -150,7 +150,7 @@ class ParamService(BaseService(Param]):
width: 100,
// 方式一:直接使用后端返回的 typeText 字段
render(record) {
return h(ElTag,
return h(Tag,
{ type: record.row.type == 0 ? 'primary' : 'warning' },
{ default: () => record.row.typeText });
},
@@ -161,7 +161,7 @@ class ParamService(BaseService(Param]):
width: 100,
// 方式二:直接使用后端返回的 statusText 字段
render(record) {
return h(ElTag,
return h(Tag,
{ type: record.row.status == 1 ? 'success' : 'danger' },
{ default: () => record.row.statusText });
},
@@ -205,7 +205,7 @@ export const schemas: FormSchema[] = [
label: '参数类型',
componentProps: {
placeholder: '请选择参数类型',
clearable: true,
allowClear: true,
options: [
{ label: '系统', value: 0 },
{ label: '业务', value: 1 },
@@ -218,7 +218,7 @@ export const schemas: FormSchema[] = [
label: '状态',
componentProps: {
placeholder: '请选择状态',
clearable: true,
allowClear: true,
options: [
{ label: '正常', value: 1 },
{ label: '禁用', value: 2 },
@@ -309,8 +309,8 @@ const options = [
// 业务字段为数字
formData.type = 0;
// el-option 绑定时需要转换
<el-option :value="Number(item.value)" />
// a-option 绑定时需要转换
<a-option :value="Number(item.value)" />
```
搜索表单中的 `options` 的 `value` 为字符串 `'0'`/`'1'`,后端 `page_eq_fields` 中的 `type` 字段期望数字。前端 API 传参时自动转换。
@@ -51,13 +51,13 @@ class PositionAddHandler(BaseHandler):
```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 属性
@@ -27,7 +27,7 @@
|:----|:----|:----|
| `UPLOAD_ALLOWED_EXTS` | `.jpg,.jpeg,.png,.gif,...,.zip` | 允许上传的文件扩展名(逗号分隔,带点号) |
| `UPLOAD_MAX_SIZE_MB` | `10` | 上传文件大小上限(MB) |
| `TORNADO_FILE_URL` | `http://file.tornado.elevue2` | 文件访问域名 |
| `TORNADO_FILE_URL` | `http://file.tornado.antdvue` | 文件访问域名 |
| `TORNADO_UPLOAD_DIR` | `uploads` | 上传文件存储目录 |
## 上传接口
@@ -173,14 +173,14 @@ Service 层的 `BaseService._enrich_file_urls()` 会自动为文件字段补此
```vue
<template>
<el-upload
<a-upload
:action="uploadUrl"
:headers="headers"
:on-success="handleSuccess"
:before-upload="beforeUpload"
>
<el-button type="primary">上传文件</el-button>
</el-upload>
<a-button type="primary">上传文件</a-button>
</a-upload>
</template>
<script setup>
@@ -196,7 +196,7 @@ const headers = {
function beforeUpload(file) {
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
ElMessage.error('文件大小不能超过 10MB');
message.error('文件大小不能超过 10MB');
}
return isLt10M;
}
@@ -214,7 +214,7 @@ function handleSuccess(response) {
```vue
<template>
<el-image
<a-image
v-if="form.cover"
:src="getFileUrl(form.cover)"
:preview-src-list="[getFileUrl(form.cover)]"
@@ -187,12 +187,12 @@ pip install -r requirements.txt
# 干跑:核对源库表和行数
python scripts/migrate_db.py --dry-run \
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-user=root --src-pass=root --src-db=djangoadmin.tornado.elevue2
--src-user=root --src-pass=root --src-db=djangoadmin.tornado.antdvue
# 或使用 Make 命令
make db-migrate-dry-run ARGS="\
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-user=root --src-pass=root --src-db=djangoadmin.tornado.elevue2"
--src-user=root --src-pass=root --src-db=djangoadmin.tornado.antdvue"
```
### MySQL → PostgreSQL
@@ -200,9 +200,9 @@ make db-migrate-dry-run ARGS="\
```bash
python scripts/migrate_db.py \
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-db=djangoadmin.tornado.elevue2 --src-user=root --src-pass=root \
--src-db=djangoadmin.tornado.antdvue --src-user=root --src-pass=root \
--dst-driver=postgresql --dst-host=localhost --dst-port=5432 \
--dst-db=djangoadmin.tornado.elevue2 --dst-user=postgres --dst-pass=你的密码 \
--dst-db=djangoadmin.tornado.antdvue --dst-user=postgres --dst-pass=你的密码 \
--drop-target-first
```
@@ -210,12 +210,12 @@ python scripts/migrate_db.py \
```
==============================================================
[迁移开始] 源: mysql:djangoadmin.tornado.elevue2 → 目标: postgresql:djangoadmin.tornado.elevue2
[迁移开始] 源: mysql:djangoadmin.tornado.antdvue → 目标: postgresql:djangoadmin.tornado.antdvue
[迁移开始] 待迁移 23 张表
==============================================================
[·] [1/5] 建目标库...
· 正在连接 postgres 维护库...
· 正在创建目标库 "djangoadmin.tornado.elevue2"(UTF8)...
· 正在创建目标库 "djangoadmin.tornado.antdvue"(UTF8)...
[✓] [1/5] 建目标库 完成(耗时 947ms)
[·] [2/5] 建表结构(23 张表 create_all)...
[✓] [2/5] 建表结构 完成(耗时 1.1s),23 张表
@@ -248,9 +248,9 @@ python scripts/migrate_db.py \
```bash
python scripts/migrate_db.py \
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-db=djangoadmin.tornado.elevue2 --src-user=root --src-pass=root \
--src-db=djangoadmin.tornado.antdvue --src-user=root --src-pass=root \
--dst-driver=mssql --dst-host=localhost --dst-port=1433 \
--dst-db=djangoadmin.tornado.elevue2 --dst-user=sa --dst-pass=你的密码 \
--dst-db=djangoadmin.tornado.antdvue --dst-user=sa --dst-pass=你的密码 \
--drop-target-first
```
@@ -262,13 +262,13 @@ python scripts/migrate_db.py \
```
==============================================================
[迁移开始] 源: mysql → 目标: mssql:djangoadmin.tornado.elevue2
[迁移开始] 源: mysql → 目标: mssql:djangoadmin.tornado.antdvue
[迁移开始] 待迁移 23 张表
==============================================================
[·] [1/5] 建目标库...
· 正在连接 SQL Server(master 维护库)...
· 正在删除旧目标库 [djangoadmin.tornado.elevue2](SINGLE_USER + DROP)...
· 正在创建目标库 [djangoadmin.tornado.elevue2]...
· 正在删除旧目标库 [djangoadmin.tornado.antdvue](SINGLE_USER + DROP)...
· 正在创建目标库 [djangoadmin.tornado.antdvue]...
[✓] [1/5] 建目标库 完成(耗时 304ms)
[·] [2/5] 建表结构(23 张表 create_all)...
[✓] [2/5] 建表结构 完成(耗时 743ms),23 张表
@@ -291,8 +291,8 @@ python scripts/migrate_db.py \
```bash
python scripts/migrate_db.py \
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-db=djangoadmin.tornado.elevue2 --src-user=root --src-pass=root \
--dst-driver=sqlite --dst-db=D:/data/djangoadmin.tornado.elevue2.db \
--src-db=djangoadmin.tornado.antdvue --src-user=root --src-pass=root \
--dst-driver=sqlite --dst-db=D:/data/djangoadmin.tornado.antdvue.db \
--drop-target-first
```
@@ -306,7 +306,7 @@ python scripts/migrate_db.py \
```
==============================================================
[迁移开始] 源: mysql → 目标: sqlite:D:/data/djangoadmin.tornado.elevue2.db
[迁移开始] 源: mysql → 目标: sqlite:D:/data/djangoadmin.tornado.antdvue.db
[迁移开始] 待迁移 23 张表
==============================================================
[·] [1/5] 建目标库...
@@ -334,7 +334,7 @@ python scripts/migrate_db.py \
```bash
python scripts/migrate_db.py \
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-db=djangoadmin.tornado.elevue2 --src-user=root --src-pass=root \
--src-db=djangoadmin.tornado.antdvue --src-user=root --src-pass=root \
--dst-driver=oracle --dst-host=localhost --dst-port=1521 \
--dst-db=ORCLPDB1 --dst-user=tornado --dst-pass=你的密码 \
--drop-target-first
@@ -29,7 +29,7 @@
**数据库**:
- [ ] 安装 MySQL 8.0+
- [ ] 创建数据库:`CREATE DATABASE djangoadmin.tornado.elevue2 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`
- [ ] 创建数据库:`CREATE DATABASE djangoadmin.tornado.antdvue DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`
- [ ] 创建数据库用户并授权
- [ ] 修改 `.env` 中的数据库配置
- [ ] 初始化数据库:`python scripts/init_db.py`
@@ -138,7 +138,7 @@ server {
### 数据库迁移
- [ ] 备份数据库:`mysqldump -uroot -p djangoadmin.tornado.elevue2 > backup.sql`
- [ ] 备份数据库:`mysqldump -uroot -p djangoadmin.tornado.antdvue > backup.sql`
- [ ] 执行迁移:`alembic upgrade head`
- [ ] 验证迁移结果
@@ -198,7 +198,7 @@ cp -r /opt/backup/dist /opt/project/ui/dist
```bash
# 从备份恢复
mysql -uroot -p djangoadmin.tornado.elevue2 < backup.sql
mysql -uroot -p djangoadmin.tornado.antdvue < backup.sql
```
## 总结
@@ -58,9 +58,9 @@ Dockerfile 将 `requirements.txt` 和源码分两步 COPY,利用 Docker 层缓
version: '3.7'
services:
tornado_elevue: # 服务名称
image: tornado_elevue # 镜像名称
container_name: tornado_elevue # 容器名称
tornado_antdvue: # 服务名称
image: tornado_antdvue # 镜像名称
container_name: tornado_antdvue # 容器名称
build: # 构建配置
context: ./ # 构建上下文路径
dockerfile: Dockerfile # Dockerfile 文件名
@@ -116,26 +116,26 @@ docker-compose build
docker-compose ps
# 查看日志
docker-compose logs -f tornado_elevue
docker-compose logs -f tornado_antdvue
# 停止服务
docker-compose down
# 重启服务
docker-compose restart tornado_elevue
docker-compose restart tornado_antdvue
# 进入容器
docker exec -it tornado_elevue bash
docker exec -it tornado_antdvue bash
```
### 查看容器资源
```bash
# 查看容器资源占用
docker stats tornado_elevue
docker stats tornado_antdvue
# 查看容器日志大小
docker inspect --format='{{.LogPath}}' tornado_elevue
docker inspect --format='{{.LogPath}}' tornado_antdvue
```
## 环境变量注入
@@ -186,7 +186,7 @@ logging:
```yaml
services:
tornado_elevue:
tornado_antdvue:
# ... 后端配置 ...
nginx:
@@ -198,7 +198,7 @@ services:
- ./nginx.conf:/etc/nginx/conf.d/default.conf # Nginx 配置文件
- ./dist:/usr/share/nginx/html # 前端构建产物
depends_on: # 依赖服务
- tornado_elevue # 等待后端服务启动
- tornado_antdvue # 等待后端服务启动
```
## 总结
@@ -249,10 +249,10 @@ python -m memory_profiler app.py
```bash
# 1. 查看容器日志
docker-compose logs tornado_elevue
docker-compose logs tornado_antdvue
# 2. 进入容器调试
docker run -it --rm tornado_elevue bash
docker run -it --rm tornado_antdvue bash
# 3. 检查 .env 文件
cat .env
@@ -131,7 +131,7 @@ query = query.filter(self._soft_col() == 0)
```
document/ # 补充文档
├── 数据库迁移_runbook.md # 数据库迁移操作手册
└── djangoadmin.tornado.elevue2.sql # 数据库结构 SQL
└── djangoadmin.tornado.antdvue.sql # 数据库结构 SQL
wiki/ # VitePress 文档站
├── zh/ # 中文文档
@@ -2,11 +2,11 @@
## 概述
前端基于 Vue3 + ElementPlus + 自定义组件库构建。页面统一使用 `PageWrapper` + `BasicForm` + `BasicTable` + `TableAction` 四件套,分页由 `BasicTable` 内置处理,无需单独引入分页组件。
前端基于 Vue3 + AntDesign + 自定义组件库构建。页面统一使用 `PageWrapper` + `BasicForm` + `BasicTable` + `TableAction` 四件套,分页由 `BasicTable` 内置处理,无需单独引入分页组件。
:::tip 技术栈
- Vue3(Composition API + `<script setup>`)
- ElementPlus
- AntDesign
- 自定义组件(BasicTable / BasicForm / TableAction / PageWrapper)
- Pinia 状态管理
- Vue Router 路由管理
@@ -56,7 +56,7 @@ ui/src/
├── utils/ # 工具函数
├── hooks/ # 组合式函数
├── styles/ # 全局样式
├── plugins/ # 插件注册(ElementPlus、自定义组件、指令)
├── plugins/ # 插件注册(AntDesign、自定义组件、指令)
└── main.ts # 入口文件
```
@@ -79,12 +79,12 @@ 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"
@@ -94,22 +94,22 @@ ui/src/
@selection-change="onSelectionChange"
>
<template #tableTitle>
<el-button type="primary" @click="handleAdd" v-perm="['sys:param:add']">
<el-icon><PlusOutlined /></el-icon>
<a-button type="primary" @click="handleAdd" v-perm="['sys:param:add']">
<PlusOutlined />
添加参数
</el-button>
<el-button
</a-button>
<a-button
type="danger"
@click="handleDelete()"
:disabled="!selectionData.length"
v-perm="['sys:param:batchDelete']"
>
<el-icon><Delete /></el-icon>
<Delete />
删除
</el-button>
</a-button>
</template>
</BasicTable>
</el-card>
</a-card>
<!-- 编辑弹窗(懒加载) -->
<editDialog
@@ -133,7 +133,7 @@ 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' }, // 多选列
@@ -146,7 +146,7 @@ export const columns = [
prop: 'type',
minWidth: 100,
render(record) {
return h(ElTag,
return h(Tag,
{ type: record.row.type == 0 ? 'primary' : 'warning' },
{ default: () => (record.row.type == 0 ? '系统' : '业务') }
);
@@ -157,7 +157,7 @@ export const columns = [
prop: 'status',
minWidth: 100,
render(record) {
return h(ElTag,
return h(Tag,
{ type: record.row.status == 1 ? 'success' : 'danger' },
{ default: () => (record.row.status == 1 ? '正常' : '禁用') }
);
@@ -172,7 +172,7 @@ export const columns = [
```typescript
// 状态标签
render(record) {
return h(ElTag,
return h(Tag,
{ type: record.row.status == 1 ? 'success' : 'danger' },
{ default: () => record.row.statusText } // 使用后端 serialize_maps 返回的 xxxText
);
@@ -206,7 +206,7 @@ export const schemas: FormSchema[] = [
label: '参数类型',
componentProps: {
placeholder: '请选择参数类型',
clearable: true,
allowClear: true,
options: [
{ label: '系统', value: '0' },
{ label: '业务', value: '1' },
@@ -219,7 +219,7 @@ export const schemas: FormSchema[] = [
label: '状态',
componentProps: {
placeholder: '请选择状态',
clearable: true,
allowClear: true,
options: [
{ label: '正常', value: '1' },
{ label: '禁用', value: '2' },
+1 -1
View File
@@ -105,7 +105,7 @@ service.py → 业务逻辑层
| 层级 | 技术 |
|------|------|
| 后端 | Tornado + SQLAlchemy + Pydantic |
| 前端 | Vue3 + ElementPlus + TypeScript + Vite |
| 前端 | Vue3 + AntDesign + TypeScript + Vite |
| 基础设施 | MySQL / PostgreSQL / Redis / Docker |
详细版本号和技术选型说明见 [技术选型](/zh/volume3/tech-stack)。
+1 -1
View File
@@ -37,7 +37,7 @@ features:
linkText: 查看更多
rel: noreferrer
- title: 🚀 全新技术栈
details: Vue3、TypeScript、Vite、Pinia、Vue Router 等技术栈,采用主流前后端分离架构实现。
details: Tornado 6、SQLAlchemy 2.0、Pydantic v2、Vue3、TypeScript、Vite、Pinia、Vue Router 等技术栈,采用主流前后端分离架构实现。
link: /zh/volume3/tech-stack
linkText: 查看更多
rel: noreferrer