refactor: 重构路由与部署脚本,更新静态资源路径
@@ -1,6 +1,6 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="backend/data/logo.png" width="150" height="150" alt="logo" />
|
<img src="frontend/docs/src/public/logo.png" width="150" height="150" alt="logo" />
|
||||||
</p>
|
</p>
|
||||||
<h1>FastApiAdmin <img src="https://img.shields.io/badge/Version-v2.0.0-blue" alt="Version"></h1>
|
<h1>FastApiAdmin <img src="https://img.shields.io/badge/Version-v2.0.0-blue" alt="Version"></h1>
|
||||||
<h3>Modern Full-Stack Rapid Development Platform</h3>
|
<h3>Modern Full-Stack Rapid Development Platform</h3>
|
||||||
@@ -191,16 +191,16 @@ With **Pydantic v2** and **PostgreSQL (asyncpg)**, ORM writes expect native Pyth
|
|||||||
|
|
||||||
| Module | Screenshot |
|
| Module | Screenshot |
|
||||||
|------------|---------------------------------|
|
|------------|---------------------------------|
|
||||||
| Login |  |
|
| Login |  |
|
||||||
| Dashboard |  |
|
| Dashboard |  |
|
||||||
| Generator |  |
|
| Generator |  |
|
||||||
| AI |  |
|
| AI |  |
|
||||||
|
|
||||||
### Mobile
|
### Mobile
|
||||||
|
|
||||||
| Login <div style="width:60px"/> | Home <div style="width:60px"/> | Profile <div style="width:60px"/> |
|
| Login <div style="width:60px"/> | Home <div style="width:60px"/> | Profile <div style="width:60px"/> |
|
||||||
|----------|----------|----------|
|
|----------|----------|----------|
|
||||||
|  |  |  |
|
|  |  |  |
|
||||||
|
|
||||||
## 🚀 Quick Start
|
## 🚀 Quick Start
|
||||||
|
|
||||||
@@ -652,7 +652,7 @@ Thanks to the contributions and support of the following open-source projects:
|
|||||||
|
|
||||||
| Group QR Code | WeChat Pay QR Code |
|
| Group QR Code | WeChat Pay QR Code |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|
||||||
## ❤️ Support the Project
|
## ❤️ Support the Project
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="backend/data/logo.png" width="150" height="150" alt="logo" />
|
<img src="frontend/docs/src/public/logo.png" width="150" height="150" alt="logo" />
|
||||||
</p>
|
</p>
|
||||||
<h1>FastApiAdmin <sup style="background-color: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 0.4em; vertical-align: super; margin-left: 5px;">v2.0.0</h1>
|
<h1>FastApiAdmin <sup style="background-color: #28a745; color: white; padding: 2px 6px; border-radius: 3px; font-size: 0.4em; vertical-align: super; margin-left: 5px;">v2.0.0</h1>
|
||||||
<h3>现代化全栈快速开发平台</h3>
|
<h3>现代化全栈快速开发平台</h3>
|
||||||
@@ -193,16 +193,16 @@ flowchart LR
|
|||||||
|
|
||||||
| 模块名 <div style="width:60px"/> | 截图 |
|
| 模块名 <div style="width:60px"/> | 截图 |
|
||||||
| ----- | --- |
|
| ----- | --- |
|
||||||
| 登录 |  |
|
| 登录 |  |
|
||||||
| 仪表盘 |  |
|
| 仪表盘 |  |
|
||||||
| 代码生成 |  |
|
| 代码生成 |  |
|
||||||
| 智能助手 |  |
|
| 智能助手 |  |
|
||||||
|
|
||||||
### 移动端
|
### 移动端
|
||||||
|
|
||||||
| 登录 <div style="width:60px"/> | 首页 <div style="width:60px"/> | 个人中心 <div style="width:60px"/> |
|
| 登录 <div style="width:60px"/> | 首页 <div style="width:60px"/> | 个人中心 <div style="width:60px"/> |
|
||||||
|----------|----------|----------|
|
|----------|----------|----------|
|
||||||
|  |  |  |
|
|  |  |  |
|
||||||
|
|
||||||
## 🚀 快速开始
|
## 🚀 快速开始
|
||||||
|
|
||||||
@@ -613,7 +613,7 @@ A:使用 `./deploy.sh` 脚本一键部署到生产环境。
|
|||||||
|
|
||||||
| 群组二维码 | 微信支付二维码 |
|
| 群组二维码 | 微信支付二维码 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|
||||||
## ❤️ 支持项目
|
## ❤️ 支持项目
|
||||||
|
|
||||||
|
|||||||
@@ -94,13 +94,6 @@ class IpLocalUtil:
|
|||||||
result = response.json().get("data", {})
|
result = response.json().get("data", {})
|
||||||
return f"{result.get('country', '')}-{result.get('prov', '')}-{result.get('city', '')}-{result.get('area', '')}-{result.get('isp', '')}"
|
return f"{result.get('country', '')}-{result.get('prov', '')}-{result.get('city', '')}-{result.get('area', '')}-{result.get('isp', '')}"
|
||||||
|
|
||||||
# 备用:ip-api.com(免费,无需 Key,45次/分钟)
|
|
||||||
url = f"http://ip-api.com/json/{ip}?lang=zh-CN"
|
|
||||||
response = await cls._make_api_request(client, url)
|
|
||||||
if response and response.json().get("status") == "success":
|
|
||||||
data = response.json()
|
|
||||||
return f"{data.get('country', '')}-{data.get('regionName', '')}-{data.get('city', '')}-{data.get('isp', '')}"
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"获取IP归属地失败: {e}")
|
log.error(f"获取IP归属地失败: {e}")
|
||||||
return "未知"
|
return "未知"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 721 KiB |
|
Before Width: | Height: | Size: 604 KiB |
|
Before Width: | Height: | Size: 563 KiB |
|
Before Width: | Height: | Size: 232 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 3.3 MiB |
|
Before Width: | Height: | Size: 1013 KiB |
|
Before Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 948 KiB |
|
Before Width: | Height: | Size: 3.0 MiB |
|
Before Width: | Height: | Size: 131 KiB |
@@ -1,446 +0,0 @@
|
|||||||
## 📘 项目介绍
|
|
||||||
|
|
||||||
**FastApiAdmin** 是一套 **完全开源、高度模块化、技术先进的现代化快速开发平台**,旨在帮助开发者高效搭建高质量的企业级中后台系统。该项目采用 **前后端分离架构**,融合 Python 后端框架 `FastAPI` 和前端主流框架 `Vue3` 实现多端统一开发,提供了一站式开箱即用的开发体验。
|
|
||||||
|
|
||||||
> **设计初心**: 以模块化、松耦合为核心,追求丰富的功能模块、简洁易用的接口、详尽的开发文档和便捷的维护方式。通过统一框架和组件,降低技术选型成本,遵循开发规范和设计模式,构建强大的代码分层模型,搭配完善的本地中文化支持,专为团队和企业开发场景量身定制。
|
|
||||||
|
|
||||||
## 🎯 核心优势
|
|
||||||
|
|
||||||
| 优势 | 描述 |
|
|
||||||
| ---- | ---- |
|
|
||||||
| 🔥 **现代化技术栈** | 基于 FastAPI + Vue3 + TypeScript 等前沿技术构建 |
|
|
||||||
| ⚡ **高性能异步** | 利用 FastAPI 异步特性和 Redis 缓存优化响应速度 |
|
|
||||||
| 🔐 **安全可靠** | JWT + OAuth2 认证机制,RBAC 权限控制模型 |
|
|
||||||
| 🧱 **模块化设计** | 高度解耦的系统架构,便于扩展和维护 |
|
|
||||||
| 🌐 **全栈支持** | Web端 + 移动端(H5) + 后端一体化解决方案 |
|
|
||||||
| 🚀 **快速部署** | Docker 一键部署,支持生产环境快速上线 |
|
|
||||||
| 📖 **完善文档** | 详细的开发文档和教程,降低学习成本 |
|
|
||||||
| 🤖 **智能体框架** | 基于Agno的开发智能体 |
|
|
||||||
|
|
||||||
## 🍪 演示环境
|
|
||||||
|
|
||||||
- 💻 网页端:[https://service.fastapiadmin.com/web](https://service.fastapiadmin.com/web)
|
|
||||||
- 📱 移动端:[https://service.fastapiadmin.com/app](https://service.fastapiadmin.com/app)
|
|
||||||
- 👤 登录账号:`admin` 密码:`123456`
|
|
||||||
|
|
||||||
## 🔗 源码仓库
|
|
||||||
|
|
||||||
| 平台 | 仓库地址 |
|
|
||||||
|------|----------|
|
|
||||||
| GitHub | [FastapiAdmin主工程](https://github.com/fastapiadmin/FastapiAdmin.git) \| [FastDocs官网](https://github.com/fastapiadmin/FastDocs.git) \| [FastApp移动端](https://github.com/fastapiadmin/FastApp.git) |
|
|
||||||
| Gitee | [FastapiAdmin主工程](https://gitee.com/fastapiadmin/FastapiAdmin.git) \| [FastDocs官网](https://gitee.com/fastapiadmin/FastDocs.git) \| [FastApp移动端](https://gitee.com/fastapiadmin/FastApp.git) |
|
|
||||||
|
|
||||||
## 📦 工程结构概览
|
|
||||||
|
|
||||||
```sh
|
|
||||||
FastapiAdmin
|
|
||||||
├─ backend # 后端工程 (FastAPI + Python)
|
|
||||||
├─ frontend # Web前端工程 (Vue3 + Element Plus)
|
|
||||||
├─ devops # 部署配置
|
|
||||||
├─ docker-compose.yaml # Docker编排文件
|
|
||||||
├─ deploy.sh # 一键部署脚本
|
|
||||||
├─ LICENSE # 开源协议
|
|
||||||
|─ README.en.md # 英文文档
|
|
||||||
└─ README.md # 中文文档
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🛠️ 技术栈概览
|
|
||||||
|
|
||||||
| 类型 | 技术选型 | 描述 |
|
|
||||||
|------|----------|------|
|
|
||||||
| **后端框架** | FastAPI / Uvicorn / Pydantic 2.0 / Alembic | 现代、高性能的异步框架,强制类型约束,数据迁移 |
|
|
||||||
| **ORM** | SQLAlchemy 2.0 | 强大的 ORM 库 |
|
|
||||||
| **定时任务** | APScheduler | 轻松实现定时任务 |
|
|
||||||
| **权限认证** | PyJWT | 实现 JWT 认证 |
|
|
||||||
| **前端框架** | Vue3 / Vite5 / Pinia / TypeScript | 快速开发 Vue3 应用 |
|
|
||||||
| **Web UI** | ElementPlus | 企业级 UI 组件库 |
|
|
||||||
| **移动端** | UniApp / Wot Design Uni | 跨端移动应用框架 |
|
|
||||||
| **数据库** | MySQL / PostgreSQL / Sqlite | 关系型和文档型数据库支持 |
|
|
||||||
| **缓存** | Redis | 高性能缓存数据库 |
|
|
||||||
| **文档** | Swagger / Redoc | 自动生成 API 文档 |
|
|
||||||
| **部署** | Docker / Nginx / Docker Compose | 容器化部署方案 |
|
|
||||||
| **智能体框架** | Agno | 基于Agno的智能体框架 |
|
|
||||||
|
|
||||||
## 📌 内置功能模块
|
|
||||||
|
|
||||||
| 模块 | 功能 | 描述 |
|
|
||||||
|------|------|------|
|
|
||||||
| 📊 **仪表盘** | 工作台、分析页 | 系统概览和数据分析 |
|
|
||||||
| ⚙️ **系统管理** | 用户、角色、菜单、部门、岗位、字典、配置、公告 | 核心系统管理功能 |
|
|
||||||
| 👀 **监控管理** | 在线用户、服务器监控、缓存监控 | 系统运行状态监控 |
|
|
||||||
| 📋 **任务管理** | 定时任务 | 异步任务调度管理 |
|
|
||||||
| 📝 **日志管理** | 操作日志 | 用户行为审计 |
|
|
||||||
| 🧰 **开发工具** | 代码生成、表单构建、接口文档 | 提升开发效率的工具 |
|
|
||||||
| 📁 **文件管理** | 文件存储 | 统一文件管理 |
|
|
||||||
|
|
||||||
## 🔧 模块展示
|
|
||||||
|
|
||||||
### web 端
|
|
||||||
|
|
||||||
| 模块名 <div style="width:60px"/> | 截图 |
|
|
||||||
| ----- | --- |
|
|
||||||
| 仪表盘 |  |
|
|
||||||
| 代码生成 |  |
|
|
||||||
| 智能助手 |  |
|
|
||||||
|
|
||||||
### 移动端
|
|
||||||
|
|
||||||
| 登录 <div style="width:60px"/> | 首页 <div style="width:60px"/> | 个人中心 <div style="width:60px"/> |
|
|
||||||
|----------|----------|----------|
|
|
||||||
|  |  |  |
|
|
||||||
|
|
||||||
## 🚀 快速开始
|
|
||||||
|
|
||||||
### 环境要求
|
|
||||||
|
|
||||||
| 类型 | 技术栈 | 版本 |
|
|
||||||
|------|--------|------|
|
|
||||||
| 后端 | Python | 3.12 ≥ 3.10 |
|
|
||||||
| 后端 | FastAPI | 0.109+ |
|
|
||||||
| 前端 | Node.js | ≥ 20.0 |
|
|
||||||
| 前端 | Vue3 | 3.3+ |
|
|
||||||
| 数据库 | MySQL/PostgreSQL | 8.0+/17+ |
|
|
||||||
| 缓存 | Redis | 7.0+ |
|
|
||||||
|
|
||||||
### 获取代码
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 克隆代码到本地
|
|
||||||
git clone https://gitee.com/fastapiadmin/FastapiAdmin.git
|
|
||||||
# 或者
|
|
||||||
git clone https://github.com/fastapiadmin/FastapiAdmin.git
|
|
||||||
```
|
|
||||||
|
|
||||||
> **后端注意**:克隆下的代码需要修改 `backend/env` 目录下的 `.env.dev.example` 文件为 `.env.dev`,修改 `backend/env` 目录下的 `.env.prod.example` 文件为 `.env.prod`,然后根据实际情况修改数据库连接信息、Redis连接信息等。
|
|
||||||
|
|
||||||
> **前端注意**:克隆下的代码需要修改 `frontend` 目录下的 `.env.development.example` 文件为 `.env.development`,修改 `frontend` 目录下的 `.env.production.example` 文件为 `.env.production`,然后根据实际情况修改接口地址等。
|
|
||||||
|
|
||||||
### 后端启动
|
|
||||||
|
|
||||||
#### 使用 uv 管理项目(推荐)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 进入后端工程目录
|
|
||||||
cd backend
|
|
||||||
# 使用 uv 安装依赖
|
|
||||||
uv add -r requirements.txt
|
|
||||||
# 启动后端服务:启动之前保证mysql中创建好了数据库、redis服务
|
|
||||||
uv run main.py run
|
|
||||||
# 或指定环境
|
|
||||||
uv run main.py run --env=dev or --env=prod
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 使用传统 pip 方式
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 进入后端工程目录
|
|
||||||
cd backend
|
|
||||||
# 安装依赖
|
|
||||||
pip3 install -r requirements.txt
|
|
||||||
# 启动后端服务:启动之前保证mysql中创建好了数据库、redis服务
|
|
||||||
python main.py run
|
|
||||||
# 或指定环境
|
|
||||||
python main.py run --env=dev or --env=prod
|
|
||||||
```
|
|
||||||
|
|
||||||
### 前端启动
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 进入前端工程目录
|
|
||||||
cd frontend
|
|
||||||
# 安装依赖
|
|
||||||
pnpm install
|
|
||||||
# 启动开发服务器
|
|
||||||
pnpm run dev
|
|
||||||
# 构建生产版本
|
|
||||||
pnpm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🐳 Docker 部署
|
|
||||||
|
|
||||||
#### 方式一:脚本放在项目内执行(推荐)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 克隆代码到服务器
|
|
||||||
git clone https://gitee.com/fastapiadmin/FastapiAdmin.git
|
|
||||||
cd FastapiAdmin
|
|
||||||
|
|
||||||
# 2. 赋予执行权限并部署
|
|
||||||
chmod +x deploy.sh
|
|
||||||
./deploy.sh
|
|
||||||
|
|
||||||
# 查看容器日志
|
|
||||||
./deploy.sh logs
|
|
||||||
|
|
||||||
# 停止服务
|
|
||||||
./deploy.sh stop
|
|
||||||
|
|
||||||
# 重启服务
|
|
||||||
./deploy.sh restart
|
|
||||||
|
|
||||||
# 更新代码并重启(不重新构建镜像,适合后端代码热更新)
|
|
||||||
./deploy.sh update
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 方式二:脚本放在项目外执行
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 将部署脚本复制到服务器
|
|
||||||
cp deploy.sh /home/
|
|
||||||
cd /home
|
|
||||||
chmod +x deploy.sh
|
|
||||||
|
|
||||||
# 2. 执行一键部署(会自动克隆项目)
|
|
||||||
./deploy.sh
|
|
||||||
|
|
||||||
# 查看容器日志
|
|
||||||
./deploy.sh logs
|
|
||||||
|
|
||||||
# 停止服务
|
|
||||||
./deploy.sh stop
|
|
||||||
|
|
||||||
# 重启服务
|
|
||||||
./deploy.sh restart
|
|
||||||
|
|
||||||
# 更新代码并重启(不重新构建镜像,适合后端代码热更新)
|
|
||||||
./deploy.sh update
|
|
||||||
```
|
|
||||||
|
|
||||||
> **注意**:
|
|
||||||
> - 首次部署时会自动拉取代码并构建镜像
|
|
||||||
> - 前端使用本地构建的 dist 目录,如需更新前端请先本地构建并提交到仓库
|
|
||||||
> - 确保 `devops/nginx/ssl/` 目录包含 SSL 证书文件(如使用 HTTPS)
|
|
||||||
|
|
||||||
## 🛠️ 二开教程
|
|
||||||
|
|
||||||
### 后端开发
|
|
||||||
|
|
||||||
项目采用**插件化架构设计**,二次开发建议在 `backend/app/plugin` 目录下进行,系统会**自动发现并注册**所有符合规范的路由,便于模块管理和升级维护。
|
|
||||||
|
|
||||||
#### 插件化架构特性
|
|
||||||
|
|
||||||
- **自动路由发现**:系统会自动扫描 `backend/app/plugin/` 目录下所有 `controller.py` 文件
|
|
||||||
- **自动路由注册**:所有路由会被自动注册到对应的前缀路径 (module_xxx -> /xxx)
|
|
||||||
- **模块化管理**:按功能模块组织代码,便于维护和扩展
|
|
||||||
- **支持多层级嵌套**:支持模块内部多层级嵌套结构
|
|
||||||
|
|
||||||
#### 插件目录结构
|
|
||||||
|
|
||||||
```sh
|
|
||||||
backend/app/plugin/
|
|
||||||
├── module_application/ # 应用模块(自动映射为 /application)
|
|
||||||
│ └── ai/ # AI子模块
|
|
||||||
│ ├── controller.py # 控制器文件
|
|
||||||
│ ├── model.py # 数据模型文件
|
|
||||||
│ ├── schema.py # 数据验证文件
|
|
||||||
│ ├── service.py # 业务逻辑文件
|
|
||||||
│ └── crud.py # 数据访问文件
|
|
||||||
├── module_example/ # 示例模块(自动映射为 /example)
|
|
||||||
│ └── demo/ # 子模块
|
|
||||||
│ ├── controller.py # 控制器文件
|
|
||||||
│ ├── model.py # 数据模型文件
|
|
||||||
│ ├── schema.py # 数据验证文件
|
|
||||||
│ ├── service.py # 业务逻辑文件
|
|
||||||
│ └── crud.py # 数据访问文件
|
|
||||||
├── module_generator/ # 代码生成模块(自动映射为 /generator)
|
|
||||||
└── init_app.py # 插件初始化文件
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 自动路由注册机制
|
|
||||||
|
|
||||||
系统会**自动发现并注册**所有符合以下条件的路由:
|
|
||||||
1. 控制器文件必须命名为 `controller.py`
|
|
||||||
2. 路由会自动映射:`module_xxx` -> `/xxx`
|
|
||||||
3. 支持多个 `APIRouter` 实例
|
|
||||||
4. 自动处理路由去重
|
|
||||||
|
|
||||||
#### 二次开发步骤
|
|
||||||
|
|
||||||
1. **创建插件模块**:在 `backend/app/plugin/` 目录下创建新的模块目录,如 `module_yourfeature`
|
|
||||||
2. **编写数据模型**:在 `model.py` 中定义数据库模型
|
|
||||||
3. **编写数据验证**:在 `schema.py` 中定义数据验证模型
|
|
||||||
4. **编写数据访问层**:在 `crud.py` 中编写数据库操作逻辑
|
|
||||||
5. **编写业务逻辑层**:在 `service.py` 中编写业务逻辑
|
|
||||||
6. **编写控制器**:在 `controller.py` 中定义路由和处理函数
|
|
||||||
7. **自动注册**:系统会自动扫描并注册所有路由,无需手动配置
|
|
||||||
|
|
||||||
#### 控制器示例
|
|
||||||
|
|
||||||
```python
|
|
||||||
# backend/app/plugin/module_yourfeature/yourcontroller/controller.py
|
|
||||||
from fastapi import APIRouter, Depends, Path
|
|
||||||
from fastapi.responses import JSONResponse
|
|
||||||
|
|
||||||
from app.common.response import SuccessResponse
|
|
||||||
from app.core.router_class import OperationLogRoute
|
|
||||||
from app.core.dependencies import AuthPermission
|
|
||||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
|
||||||
from .service import YourFeatureService
|
|
||||||
|
|
||||||
# 创建路由实例
|
|
||||||
YourFeatureRouter = APIRouter(
|
|
||||||
route_class=OperationLogRoute,
|
|
||||||
prefix="/yourcontroller",
|
|
||||||
tags=["你的功能模块"]
|
|
||||||
)
|
|
||||||
|
|
||||||
@YourFeatureRouter.get("/detail/{id}", summary="获取详情")
|
|
||||||
async def get_detail(
|
|
||||||
id: int = Path(..., description="功能ID"),
|
|
||||||
auth: AuthSchema = Depends(AuthPermission(["module_yourfeature:yourcontroller:detail"]))
|
|
||||||
) -> JSONResponse:
|
|
||||||
result = await YourFeatureService.detail_service(id=id, auth=auth)
|
|
||||||
return SuccessResponse(data=result)
|
|
||||||
|
|
||||||
@YourFeatureRouter.get("/list", summary="获取列表")
|
|
||||||
async def get_list(
|
|
||||||
auth: AuthSchema = Depends(AuthPermission(["module_yourfeature:yourcontroller:list"]))
|
|
||||||
) -> JSONResponse:
|
|
||||||
result = await YourFeatureService.list_service(auth=auth)
|
|
||||||
return SuccessResponse(data=result)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 开发规范
|
|
||||||
|
|
||||||
1. **命名规范**:模块名采用 `module_xxx` 格式,控制器名采用驼峰命名法
|
|
||||||
2. **权限控制**:所有API接口必须添加权限控制装饰器
|
|
||||||
3. **日志记录**:使用 `OperationLogRoute` 类自动记录操作日志
|
|
||||||
4. **返回格式**:统一使用 `SuccessResponse` 或 `ErrorResponse` 返回响应
|
|
||||||
5. **代码注释**:为所有API接口添加详细的文档字符串
|
|
||||||
|
|
||||||
#### 注意事项
|
|
||||||
|
|
||||||
- 插件模块名必须以 `module_` 开头
|
|
||||||
- 控制器文件必须命名为 `controller.py`
|
|
||||||
- 路由会自动映射到对应的前缀路径
|
|
||||||
- 无需手动注册路由,系统会自动发现并注册
|
|
||||||
|
|
||||||
### 前端部分
|
|
||||||
|
|
||||||
1. **配置前端API**:在 `frontend/src/api/` 目录下创建对应的API文件
|
|
||||||
2. **编写页面组件**:在 `frontend/src/views/` 目录下创建页面组件
|
|
||||||
3. **注册路由**:在 `frontend/src/router/index.ts` 中注册路由
|
|
||||||
|
|
||||||
### 代码生成器使用
|
|
||||||
|
|
||||||
项目内置代码生成器,可以根据数据库表结构自动生成前后端代码,大幅提升开发效率。
|
|
||||||
|
|
||||||
#### 生成步骤
|
|
||||||
|
|
||||||
1. **登录系统**:使用管理员账号登录系统
|
|
||||||
2. **进入代码生成模块**:在左侧菜单中点击"代码生成"
|
|
||||||
3. **导入表结构**:选择要生成代码的数据库表
|
|
||||||
4. **配置生成参数**:填写模块名称、功能名称等
|
|
||||||
5. **生成代码**:点击"生成代码"按钮
|
|
||||||
6. **下载或写入**:选择下载代码包或直接写入项目目录
|
|
||||||
|
|
||||||
#### 生成文件结构
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# 后端文件
|
|
||||||
backend/app/plugin/module_yourmodule/
|
|
||||||
└── yourfeature/
|
|
||||||
├── controller.py # 控制器文件
|
|
||||||
├── model.py # 数据模型文件
|
|
||||||
├── schema.py # 数据验证文件
|
|
||||||
├── service.py # 业务逻辑文件
|
|
||||||
└── crud.py # 数据访问文件
|
|
||||||
|
|
||||||
# 前端文件
|
|
||||||
frontend/src/
|
|
||||||
├── api/module_yourmodule/
|
|
||||||
│ └── yourfeature.ts # API调用文件
|
|
||||||
└── views/module_yourmodule/
|
|
||||||
└── yourfeature/
|
|
||||||
└── index.vue # 页面组件
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 生成代码示例
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 生成的控制器代码示例
|
|
||||||
from fastapi import APIRouter, Depends
|
|
||||||
from fastapi.responses import JSONResponse
|
|
||||||
|
|
||||||
from app.common.response import SuccessResponse
|
|
||||||
from app.core.router_class import OperationLogRoute
|
|
||||||
from app.core.dependencies import AuthPermission
|
|
||||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
|
||||||
from .service import YourFeatureService
|
|
||||||
from .schema import (
|
|
||||||
YourFeatureCreateSchema,
|
|
||||||
YourFeatureUpdateSchema,
|
|
||||||
YourFeatureQueryParam
|
|
||||||
)
|
|
||||||
|
|
||||||
YourFeatureRouter = APIRouter(
|
|
||||||
route_class=OperationLogRoute,
|
|
||||||
prefix="/yourfeature",
|
|
||||||
tags=["你的功能模块"]
|
|
||||||
)
|
|
||||||
|
|
||||||
@YourFeatureRouter.get("/detail/{id}")
|
|
||||||
async def get_detail(
|
|
||||||
id: int,
|
|
||||||
auth: AuthSchema = Depends(AuthPermission(["module_yourmodule:yourfeature:detail"]))
|
|
||||||
) -> JSONResponse:
|
|
||||||
result = await YourFeatureService.detail_service(id=id, auth=auth)
|
|
||||||
return SuccessResponse(data=result)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 开发工具
|
|
||||||
|
|
||||||
- **代码生成器**:自动生成前后端CRUD代码
|
|
||||||
- **API文档**:自动生成Swagger/Redoc API文档
|
|
||||||
- **数据库迁移**:支持Alembic数据库迁移
|
|
||||||
- **日志系统**:内置日志记录和查询功能
|
|
||||||
- **监控系统**:内置服务器监控和缓存监控功能
|
|
||||||
|
|
||||||
### 开发流程
|
|
||||||
|
|
||||||
1. **需求分析**:明确功能需求和业务逻辑
|
|
||||||
2. **数据库设计**:设计数据库表结构
|
|
||||||
3. **代码生成**:使用代码生成器生成基础代码
|
|
||||||
4. **业务逻辑开发**:完善业务逻辑和接口
|
|
||||||
5. **前端开发**:开发前端页面和交互
|
|
||||||
6. **测试**:进行单元测试和集成测试
|
|
||||||
7. **部署**:部署到生产环境
|
|
||||||
|
|
||||||
### 开发注意事项
|
|
||||||
|
|
||||||
1. **权限控制**:所有API接口必须添加权限控制
|
|
||||||
2. **数据验证**:所有输入数据必须进行验证
|
|
||||||
3. **异常处理**:统一处理API异常
|
|
||||||
4. **日志记录**:关键操作必须记录日志
|
|
||||||
5. **性能优化**:注意API性能优化,避免慢查询
|
|
||||||
6. **代码规范**:遵循PEP8和项目代码规范
|
|
||||||
|
|
||||||
### 常见问题
|
|
||||||
|
|
||||||
#### Q:如何添加新功能模块?
|
|
||||||
A:按照二次开发步骤,在 `backend/app/plugin/` 目录下创建新的模块目录,编写相关代码即可。
|
|
||||||
|
|
||||||
#### Q:如何配置数据库?
|
|
||||||
A:在 `backend/env/.env.dev` 或 `backend/env/.env.prod` 文件中配置数据库连接信息。
|
|
||||||
|
|
||||||
#### Q:如何配置Redis?
|
|
||||||
A:在 `backend/env/.env.dev` 或 `backend/env/.env.prod` 文件中配置Redis连接信息。
|
|
||||||
|
|
||||||
#### Q:如何生成数据库迁移文件?
|
|
||||||
A:使用 `python main.py revision --env=dev` 命令生成迁移文件。
|
|
||||||
|
|
||||||
#### Q:如何应用数据库迁移?
|
|
||||||
A:使用 `python main.py upgrade --env=dev` 命令应用迁移。
|
|
||||||
|
|
||||||
#### Q:如何启动开发服务器?
|
|
||||||
A:使用 `python main.py run --env=dev` 命令启动开发服务器。
|
|
||||||
|
|
||||||
#### Q:如何构建前端生产版本?
|
|
||||||
A:使用 `pnpm run build` 命令构建前端生产版本。
|
|
||||||
|
|
||||||
#### Q:如何部署到生产环境?
|
|
||||||
A:使用 `./deploy.sh` 脚本一键部署到生产环境。
|
|
||||||
|
|
||||||
## ℹ️ 帮助
|
|
||||||
|
|
||||||
更多详情请查看 [官方文档](https://service.fastapiadmin.com)
|
|
||||||
@@ -3,61 +3,71 @@
|
|||||||
<defs>
|
<defs>
|
||||||
<style>
|
<style>
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
#bg-layer { fill: url(#bgDark); }
|
#bg-rect { fill: #101a29; }
|
||||||
#soft-glow { fill: url(#glowDark); }
|
#blueGlow-rect { fill: url(#blueGlowDark); }
|
||||||
.accent-arc { stroke: rgba(118, 156, 255, 0.35); }
|
#blueGlow2-rect { fill: url(#blueGlow2Dark); }
|
||||||
.accent-dot { fill: rgba(154, 188, 255, 0.45); }
|
#pinkPurpleGlow-rect { fill: url(#pinkPurpleGlowDark); }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<linearGradient id="bgLight" x1="0%" y1="0%" x2="100%" y2="100%">
|
<!-- 亮色主题渐变 -->
|
||||||
<stop offset="0%" stop-color="#f3f7ff" />
|
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
<stop offset="60%" stop-color="#e3edff" />
|
<stop offset="0%" stop-color="#f9fcff" />
|
||||||
<stop offset="100%" stop-color="#d6e7ff" />
|
<stop offset="100%" stop-color="#f5f9fd" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
|
|
||||||
<linearGradient id="bgDark" x1="0%" y1="0%" x2="100%" y2="100%">
|
<!-- 中间区域淡蓝白光晕 -->
|
||||||
<stop offset="0%" stop-color="#0b1324" />
|
<radialGradient id="blueGlow" cx="50%" cy="50%" r="70%" fx="50%" fy="50%">
|
||||||
<stop offset="60%" stop-color="#162135" />
|
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.9" />
|
||||||
<stop offset="100%" stop-color="#1e2c44" />
|
<stop offset="50%" stop-color="#f0f8ff" stop-opacity="0.5" />
|
||||||
</linearGradient>
|
<stop offset="100%" stop-color="#eef7fd" stop-opacity="0" />
|
||||||
|
|
||||||
<radialGradient id="glowLight" cx="20%" cy="15%" r="60%">
|
|
||||||
<stop offset="0%" stop-color="rgba(64,128,255,0.35)" />
|
|
||||||
<stop offset="40%" stop-color="rgba(64,128,255,0.18)" />
|
|
||||||
<stop offset="100%" stop-color="rgba(64,128,255,0)" />
|
|
||||||
</radialGradient>
|
</radialGradient>
|
||||||
|
|
||||||
<radialGradient id="glowDark" cx="20%" cy="15%" r="60%">
|
<!-- 左上角蓝白光晕 -->
|
||||||
<stop offset="0%" stop-color="rgba(98,142,255,0.4)" />
|
<radialGradient id="blueGlow2" cx="15%" cy="15%" r="40%" fx="15%" fy="15%">
|
||||||
<stop offset="50%" stop-color="rgba(98,142,255,0.18)" />
|
<stop offset="0%" stop-color="#d9efff" stop-opacity="0.85" />
|
||||||
<stop offset="100%" stop-color="rgba(98,142,255,0)" />
|
<stop offset="40%" stop-color="#e5f4fd" stop-opacity="0.6" />
|
||||||
|
<stop offset="100%" stop-color="#e9f5fd" stop-opacity="0" />
|
||||||
</radialGradient>
|
</radialGradient>
|
||||||
|
|
||||||
<radialGradient id="glowSecondary" cx="80%" cy="70%" r="55%">
|
<!-- 右下角粉紫色光晕 -->
|
||||||
<stop offset="0%" stop-color="rgba(22,93,255,0.3)" />
|
<radialGradient id="pinkPurpleGlow" cx="85%" cy="85%" r="40%" fx="85%" fy="85%">
|
||||||
<stop offset="50%" stop-color="rgba(22,93,255,0.12)" />
|
<stop offset="0%" stop-color="#f7e6f9" stop-opacity="0.8" />
|
||||||
<stop offset="100%" stop-color="rgba(22,93,255,0)" />
|
<stop offset="35%" stop-color="#f9edf8" stop-opacity="0.6" />
|
||||||
|
<stop offset="100%" stop-color="#f8f2f8" stop-opacity="0" />
|
||||||
</radialGradient>
|
</radialGradient>
|
||||||
|
|
||||||
<linearGradient id="meshLight" x1="0%" y1="0%" x2="100%" y2="100%">
|
<!-- 暗色主题渐变 -->
|
||||||
<stop offset="0%" stop-color="rgba(255,255,255,0.6)" />
|
<radialGradient id="blueGlowDark" cx="50%" cy="50%" r="70%" fx="50%" fy="50%">
|
||||||
<stop offset="35%" stop-color="rgba(255,255,255,0.2)" />
|
<stop offset="0%" stop-color="#1e3a5e" stop-opacity="0.6" />
|
||||||
<stop offset="100%" stop-color="rgba(255,255,255,0)" />
|
<stop offset="50%" stop-color="#1c314e" stop-opacity="0.3" />
|
||||||
</linearGradient>
|
<stop offset="100%" stop-color="#1a2d47" stop-opacity="0" />
|
||||||
|
</radialGradient>
|
||||||
|
|
||||||
|
<!-- 左上角蓝白光晕 - 暗色模式 -->
|
||||||
|
<radialGradient id="blueGlow2Dark" cx="15%" cy="15%" r="40%" fx="15%" fy="15%">
|
||||||
|
<stop offset="0%" stop-color="#1e3858" stop-opacity="0.85" />
|
||||||
|
<stop offset="40%" stop-color="#1a304f" stop-opacity="0.6" />
|
||||||
|
<stop offset="100%" stop-color="#172b45" stop-opacity="0" />
|
||||||
|
</radialGradient>
|
||||||
|
|
||||||
|
<!-- 右下角粉紫色光晕 - 暗色模式 -->
|
||||||
|
<radialGradient id="pinkPurpleGlowDark" cx="85%" cy="85%" r="40%" fx="85%" fy="85%">
|
||||||
|
<stop offset="0%" stop-color="#2e2335" stop-opacity="0.85" />
|
||||||
|
<stop offset="35%" stop-color="#2a2035" stop-opacity="0.6" />
|
||||||
|
<stop offset="100%" stop-color="#2a202d" stop-opacity="0" />
|
||||||
|
</radialGradient>
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
<rect id="bg-layer" width="100%" height="100%" fill="url(#bgLight)" />
|
<!-- 背景层 -->
|
||||||
<rect id="soft-glow" width="100%" height="100%" fill="url(#glowLight)" />
|
<rect id="bg-rect" width="100%" height="100%" fill="url(#bgGradient)" />
|
||||||
<rect width="100%" height="100%" fill="url(#glowSecondary)" />
|
|
||||||
<rect width="100%" height="100%" fill="url(#meshLight)" />
|
|
||||||
|
|
||||||
<!-- 柔和块面光影,替代明显线条 -->
|
<!-- 中间淡蓝白光晕 -->
|
||||||
<g opacity="0.45">
|
<rect id="blueGlow-rect" width="100%" height="100%" fill="url(#blueGlow)" />
|
||||||
<rect x="-40" y="520" width="520" height="220" rx="180" fill="rgba(255,255,255,0.25)" />
|
|
||||||
<rect x="760" y="90" width="520" height="210" rx="180" fill="rgba(255,255,255,0.22)" />
|
|
||||||
<rect x="420" y="620" width="560" height="190" rx="180" fill="rgba(255,255,255,0.18)" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<!-- 去掉点状噪声,仅保留大区域柔光 -->
|
<!-- 左上蓝白光晕 -->
|
||||||
|
<rect id="blueGlow2-rect" width="100%" height="100%" fill="url(#blueGlow2)" />
|
||||||
|
|
||||||
|
<!-- 右下粉紫光晕 -->
|
||||||
|
<rect id="pinkPurpleGlow-rect" width="100%" height="100%" fill="url(#pinkPurpleGlow)" />
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -1,73 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1400 800">
|
|
||||||
<defs>
|
|
||||||
<style>
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
#bg-rect { fill: #101a29; }
|
|
||||||
#blueGlow-rect { fill: url(#blueGlowDark); }
|
|
||||||
#blueGlow2-rect { fill: url(#blueGlow2Dark); }
|
|
||||||
#pinkPurpleGlow-rect { fill: url(#pinkPurpleGlowDark); }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<!-- 亮色主题渐变 -->
|
|
||||||
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
||||||
<stop offset="0%" stop-color="#f9fcff" />
|
|
||||||
<stop offset="100%" stop-color="#f5f9fd" />
|
|
||||||
</linearGradient>
|
|
||||||
|
|
||||||
<!-- 中间区域淡蓝白光晕 -->
|
|
||||||
<radialGradient id="blueGlow" cx="50%" cy="50%" r="70%" fx="50%" fy="50%">
|
|
||||||
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.9" />
|
|
||||||
<stop offset="50%" stop-color="#f0f8ff" stop-opacity="0.5" />
|
|
||||||
<stop offset="100%" stop-color="#eef7fd" stop-opacity="0" />
|
|
||||||
</radialGradient>
|
|
||||||
|
|
||||||
<!-- 左上角蓝白光晕 -->
|
|
||||||
<radialGradient id="blueGlow2" cx="15%" cy="15%" r="40%" fx="15%" fy="15%">
|
|
||||||
<stop offset="0%" stop-color="#d9efff" stop-opacity="0.85" />
|
|
||||||
<stop offset="40%" stop-color="#e5f4fd" stop-opacity="0.6" />
|
|
||||||
<stop offset="100%" stop-color="#e9f5fd" stop-opacity="0" />
|
|
||||||
</radialGradient>
|
|
||||||
|
|
||||||
<!-- 右下角粉紫色光晕 -->
|
|
||||||
<radialGradient id="pinkPurpleGlow" cx="85%" cy="85%" r="40%" fx="85%" fy="85%">
|
|
||||||
<stop offset="0%" stop-color="#f7e6f9" stop-opacity="0.8" />
|
|
||||||
<stop offset="35%" stop-color="#f9edf8" stop-opacity="0.6" />
|
|
||||||
<stop offset="100%" stop-color="#f8f2f8" stop-opacity="0" />
|
|
||||||
</radialGradient>
|
|
||||||
|
|
||||||
<!-- 暗色主题渐变 -->
|
|
||||||
<radialGradient id="blueGlowDark" cx="50%" cy="50%" r="70%" fx="50%" fy="50%">
|
|
||||||
<stop offset="0%" stop-color="#1e3a5e" stop-opacity="0.6" />
|
|
||||||
<stop offset="50%" stop-color="#1c314e" stop-opacity="0.3" />
|
|
||||||
<stop offset="100%" stop-color="#1a2d47" stop-opacity="0" />
|
|
||||||
</radialGradient>
|
|
||||||
|
|
||||||
<!-- 左上角蓝白光晕 - 暗色模式 -->
|
|
||||||
<radialGradient id="blueGlow2Dark" cx="15%" cy="15%" r="40%" fx="15%" fy="15%">
|
|
||||||
<stop offset="0%" stop-color="#1e3858" stop-opacity="0.85" />
|
|
||||||
<stop offset="40%" stop-color="#1a304f" stop-opacity="0.6" />
|
|
||||||
<stop offset="100%" stop-color="#172b45" stop-opacity="0" />
|
|
||||||
</radialGradient>
|
|
||||||
|
|
||||||
<!-- 右下角粉紫色光晕 - 暗色模式 -->
|
|
||||||
<radialGradient id="pinkPurpleGlowDark" cx="85%" cy="85%" r="40%" fx="85%" fy="85%">
|
|
||||||
<stop offset="0%" stop-color="#2e2335" stop-opacity="0.85" />
|
|
||||||
<stop offset="35%" stop-color="#2a2035" stop-opacity="0.6" />
|
|
||||||
<stop offset="100%" stop-color="#2a202d" stop-opacity="0" />
|
|
||||||
</radialGradient>
|
|
||||||
</defs>
|
|
||||||
|
|
||||||
<!-- 背景层 -->
|
|
||||||
<rect id="bg-rect" width="100%" height="100%" fill="url(#bgGradient)" />
|
|
||||||
|
|
||||||
<!-- 中间淡蓝白光晕 -->
|
|
||||||
<rect id="blueGlow-rect" width="100%" height="100%" fill="url(#blueGlow)" />
|
|
||||||
|
|
||||||
<!-- 左上蓝白光晕 -->
|
|
||||||
<rect id="blueGlow2-rect" width="100%" height="100%" fill="url(#blueGlow2)" />
|
|
||||||
|
|
||||||
<!-- 右下粉紫光晕 -->
|
|
||||||
<rect id="pinkPurpleGlow-rect" width="100%" height="100%" fill="url(#pinkPurpleGlow)" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.2 KiB |
@@ -1,71 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="full" width="100%" height="100%" viewBox="0 0 1400 800">
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--blue: rgba(64, 158, 255, 0.08);
|
|
||||||
--grey: rgba(144, 147, 153, 0.05);
|
|
||||||
--orange: rgba(230, 162, 60, 0.06);
|
|
||||||
--green: rgba(144, 238, 144, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--blue: rgba(64, 158, 255, 0.04);
|
|
||||||
--grey: rgba(144, 147, 153, 0.03);
|
|
||||||
--orange: rgba(230, 162, 60, 0.04);
|
|
||||||
--green: rgba(144, 238, 144, 0.04);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<!-- 左侧波浪 -->
|
|
||||||
<path d="M-50 550 Q200 500 450 550 T950 530"
|
|
||||||
fill="none"
|
|
||||||
stroke="#409EFF"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke-opacity="0.05"
|
|
||||||
stroke-linecap="round">
|
|
||||||
</path>
|
|
||||||
|
|
||||||
<!-- 右侧波浪 -->
|
|
||||||
<path d="M450 650 Q800 620 1150 660 T1550 630"
|
|
||||||
fill="none"
|
|
||||||
stroke="#909399"
|
|
||||||
stroke-width="1"
|
|
||||||
stroke-opacity="0.03"
|
|
||||||
stroke-linecap="round">
|
|
||||||
</path>
|
|
||||||
|
|
||||||
<!-- 右下方圆形 -->
|
|
||||||
<circle cx="950" cy="400" r="70"
|
|
||||||
fill="var(--blue)"
|
|
||||||
stroke="#409EFF"
|
|
||||||
stroke-width="1"
|
|
||||||
stroke-opacity="0.05">
|
|
||||||
</circle>
|
|
||||||
|
|
||||||
<!-- 左上方半球形 -->
|
|
||||||
<g transform="rotate(-10, 300, 180)">
|
|
||||||
<path d="M 180 180 A 120 120 0 1 1 420 180 Q420 195 405 195 L 310 195 L 195 195 Q180 195 180 180"
|
|
||||||
fill="var(--orange)"
|
|
||||||
stroke="#E6A23C"
|
|
||||||
stroke-width="1"
|
|
||||||
stroke-opacity="0.05">
|
|
||||||
</path>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<!-- 左下方三角形 -->
|
|
||||||
<path d="M300 600 L380 520 L420 650 Z"
|
|
||||||
fill="var(--green)"
|
|
||||||
stroke="#909399"
|
|
||||||
stroke-width="1"
|
|
||||||
stroke-opacity="0.04">
|
|
||||||
</path>
|
|
||||||
|
|
||||||
<!-- 旋转方块 -->
|
|
||||||
<rect x="1000" y="420" rx="10" ry="10" width="60" height="60" fill="rgba(169, 174, 184, 0.1)" stroke="rgba(169, 174, 184, 0.2)" stroke-width="1" opacity="0.5">
|
|
||||||
<animateTransform attributeType="XML" attributeName="transform"
|
|
||||||
begin="0s" dur="30s" type="rotate"
|
|
||||||
from="0 1450 550" to="360 1450 550"
|
|
||||||
repeatCount="indefinite"/>
|
|
||||||
</rect>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 948 KiB |
@@ -2,26 +2,9 @@
|
|||||||
setlocal EnableDelayedExpansion
|
setlocal EnableDelayedExpansion
|
||||||
|
|
||||||
set "SCRIPT_DIR=%~dp0"
|
set "SCRIPT_DIR=%~dp0"
|
||||||
set "BACKEND_DIR=%SCRIPT_DIR%backend"
|
|
||||||
set "FRONTEND_DIR=%SCRIPT_DIR%frontend"
|
|
||||||
set "WEB_DIR=%FRONTEND_DIR%\web"
|
|
||||||
set "APP_DIR=%FRONTEND_DIR%\app"
|
|
||||||
set "DOCS_DIR=%FRONTEND_DIR%\docs"
|
|
||||||
|
|
||||||
set "LOG_DIR=%SCRIPT_DIR%logs"
|
set "LOG_DIR=%SCRIPT_DIR%logs"
|
||||||
mkdir "%LOG_DIR%" 2>nul
|
if not exist "%LOG_DIR%" mkdir "%LOG_DIR%"
|
||||||
|
|
||||||
set "BACKEND_LOG=%LOG_DIR%\backend.log"
|
|
||||||
set "WEB_LOG=%LOG_DIR%\web.log"
|
|
||||||
set "APP_LOG=%LOG_DIR%\app.log"
|
|
||||||
set "DOCS_LOG=%LOG_DIR%\docs.log"
|
|
||||||
|
|
||||||
set "BACKEND_PID_FILE=%LOG_DIR%\backend.pid"
|
|
||||||
set "WEB_PID_FILE=%LOG_DIR%\web.pid"
|
|
||||||
set "APP_PID_FILE=%LOG_DIR%\app.pid"
|
|
||||||
set "DOCS_PID_FILE=%LOG_DIR%\docs.pid"
|
|
||||||
|
|
||||||
:: 颜色定义
|
|
||||||
set "RED=[91m"
|
set "RED=[91m"
|
||||||
set "GREEN=[92m"
|
set "GREEN=[92m"
|
||||||
set "YELLOW=[93m"
|
set "YELLOW=[93m"
|
||||||
@@ -29,12 +12,8 @@ set "BLUE=[94m"
|
|||||||
set "CYAN=[96m"
|
set "CYAN=[96m"
|
||||||
set "RESET=[0m"
|
set "RESET=[0m"
|
||||||
|
|
||||||
echo %GREEN%========================================%RESET%
|
|
||||||
echo %GREEN%📦 FastapiAdmin 全局启动脚本%RESET%
|
|
||||||
echo %GREEN%========================================%RESET%
|
|
||||||
echo.
|
|
||||||
|
|
||||||
if "%1"=="" goto :show_help
|
if "%1"=="" goto :show_help
|
||||||
|
|
||||||
if "%1"=="start" goto :start_all
|
if "%1"=="start" goto :start_all
|
||||||
if "%1"=="stop" goto :stop_all
|
if "%1"=="stop" goto :stop_all
|
||||||
if "%1"=="restart" goto :restart_all
|
if "%1"=="restart" goto :restart_all
|
||||||
@@ -46,313 +25,140 @@ if "%1"=="-h" goto :show_help
|
|||||||
echo %RED%❌ 未知参数: %1%RESET%
|
echo %RED%❌ 未知参数: %1%RESET%
|
||||||
goto :show_help
|
goto :show_help
|
||||||
|
|
||||||
|
:: ==================== 帮助 ====================
|
||||||
:show_help
|
:show_help
|
||||||
echo %BLUE%使用说明:%RESET%
|
echo %BLUE%使用说明:%RESET%
|
||||||
echo %GREEN% %~nx0 start %RESET% - 启动所有服务
|
echo %~nx0 start - 启动所有服务
|
||||||
set "YELLOW=[93m"
|
echo %~nx0 stop - 停止所有服务
|
||||||
echo %GREEN% %~nx0 stop %RESET% - 停止所有服务
|
echo %~nx0 restart - 重启所有服务
|
||||||
set "YELLOW=[93m"
|
echo %~nx0 status - 检查服务状态
|
||||||
echo %GREEN% %~nx0 restart %RESET% - 重启所有服务
|
|
||||||
set "YELLOW=[93m"
|
|
||||||
echo %GREEN% %~nx0 status %RESET% - 检查服务状态
|
|
||||||
set "YELLOW=[93m"
|
|
||||||
echo %GREEN% %~nx0 help %RESET% - 显示此帮助信息
|
|
||||||
set "YELLOW=[93m"
|
|
||||||
echo.
|
echo.
|
||||||
echo %BLUE%服务说明:%RESET%
|
echo %BLUE%服务说明:%RESET%
|
||||||
echo %GREEN% 🚀 后端服务 %RESET% - FastAPI 后端 (默认端口: 8001)
|
echo 🚀 后端服务 - FastAPI 后端 (端口: 8001)
|
||||||
echo %GREEN% 🌐 前端 Web %RESET% - Vue 前端 (默认端口: 5173)
|
echo 🌐 前端 Web - Vue 前端 (端口: 5173)
|
||||||
echo %GREEN% 📱 前端 App %RESET% - UniApp 应用 (默认端口: 8080)
|
echo 📱 前端 App - UniApp 应用 (端口: 8080)
|
||||||
echo %GREEN% 📚 文档服务 %RESET% - VitePress 文档 (默认端口: 5174)
|
echo 📚 文档服务 - VitePress 文档 (端口: 5174)
|
||||||
echo.
|
echo.
|
||||||
echo %BLUE%日志位置:%RESET%
|
echo %BLUE%日志位置:%RESET%
|
||||||
echo %GREEN% %LOG_DIR%\%RESET%
|
echo %LOG_DIR%\
|
||||||
goto :end
|
goto :end
|
||||||
|
|
||||||
|
:: ==================== 启动 ====================
|
||||||
:start_all
|
:start_all
|
||||||
echo %CYAN%🚀 开始启动所有服务...%RESET%
|
echo %CYAN%🚀 开始启动所有服务...%RESET%
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
:: 启动后端
|
call :start_service "backend" "%SCRIPT_DIR%backend" "uv run main.py run --env=dev" "FastapiAdmin.Backend" "%BACKEND_PID%"
|
||||||
echo %BLUE%🔄 启动后端服务...%RESET%
|
call :start_service "frontend" "%SCRIPT_DIR%frontend\web" "pnpm dev" "FastapiAdmin.Web" "%WEB_PID%"
|
||||||
if exist "%BACKEND_PID_FILE%" (
|
call :start_service "app" "%SCRIPT_DIR%frontend\app" "pnpm dev:h5" "FastapiAdmin.App" "%APP_PID%"
|
||||||
set /p PID=<"%BACKEND_PID_FILE%"
|
call :start_service "docs" "%SCRIPT_DIR%frontend\docs" "pnpm dev" "FastapiAdmin.Docs" "%DOCS_PID%"
|
||||||
tasklist /FI "PID eq %PID%" 2>NUL | find /I "%PID%" >NUL
|
|
||||||
if %errorlevel%==0 (
|
|
||||||
echo %YELLOW%⚠️ 后端服务已在运行 (PID: %PID%)%RESET%
|
|
||||||
) else (
|
|
||||||
echo %GREEN%✅ 启动后端服务...%RESET%
|
|
||||||
cd /d "%BACKEND_DIR%"
|
|
||||||
start "FastapiAdmin Backend" /B uv run main.py run --env=dev > "%BACKEND_LOG%" 2>&1
|
|
||||||
:: 获取新启动进程的 PID
|
|
||||||
for /f "tokens=2" %%a in ('tasklist /FI "WINDOWTITLE eq FastapiAdmin Backend" /NH') do (
|
|
||||||
echo %%a > "%BACKEND_PID_FILE%"
|
|
||||||
echo %GREEN%✅ 后端服务已启动 (PID: %%a)%RESET%
|
|
||||||
)
|
|
||||||
cd /d "%SCRIPT_DIR%"
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
echo %GREEN%✅ 启动后端服务...%RESET%
|
|
||||||
cd /d "%BACKEND_DIR%"
|
|
||||||
start "FastapiAdmin Backend" /B uv run main.py run --env=dev > "%BACKEND_LOG%" 2>&1
|
|
||||||
:: 获取新启动进程的 PID
|
|
||||||
for /f "tokens=2" %%a in ('tasklist /FI "WINDOWTITLE eq FastapiAdmin Backend" /NH') do (
|
|
||||||
echo %%a > "%BACKEND_PID_FILE%"
|
|
||||||
echo %GREEN%✅ 后端服务已启动 (PID: %%a)%RESET%
|
|
||||||
)
|
|
||||||
cd /d "%SCRIPT_DIR%"
|
|
||||||
)
|
|
||||||
echo.
|
|
||||||
|
|
||||||
:: 启动前端 Web
|
|
||||||
echo %BLUE%🔄 启动前端 Web 服务...%RESET%
|
|
||||||
if exist "%WEB_PID_FILE%" (
|
|
||||||
set /p PID=<"%WEB_PID_FILE%"
|
|
||||||
tasklist /FI "PID eq %PID%" 2>NUL | find /I "%PID%" >NUL
|
|
||||||
if %errorlevel%==0 (
|
|
||||||
echo %YELLOW%⚠️ 前端 Web 服务已在运行 (PID: %PID%)%RESET%
|
|
||||||
) else (
|
|
||||||
echo %GREEN%✅ 启动前端 Web 服务...%RESET%
|
|
||||||
cd /d "%WEB_DIR%"
|
|
||||||
start "FastapiAdmin Web" /B pnpm dev > "%WEB_LOG%" 2>&1
|
|
||||||
:: 获取新启动进程的 PID
|
|
||||||
for /f "tokens=2" %%a in ('tasklist /FI "WINDOWTITLE eq FastapiAdmin Web" /NH') do (
|
|
||||||
echo %%a > "%WEB_PID_FILE%"
|
|
||||||
echo %GREEN%✅ 前端 Web 服务已启动 (PID: %%a)%RESET%
|
|
||||||
)
|
|
||||||
cd /d "%SCRIPT_DIR%"
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
echo %GREEN%✅ 启动前端 Web 服务...%RESET%
|
|
||||||
cd /d "%WEB_DIR%"
|
|
||||||
start "FastapiAdmin Web" /B pnpm dev > "%WEB_LOG%" 2>&1
|
|
||||||
:: 获取新启动进程的 PID
|
|
||||||
for /f "tokens=2" %%a in ('tasklist /FI "WINDOWTITLE eq FastapiAdmin Web" /NH') do (
|
|
||||||
echo %%a > "%WEB_PID_FILE%"
|
|
||||||
echo %GREEN%✅ 前端 Web 服务已启动 (PID: %%a)%RESET%
|
|
||||||
)
|
|
||||||
cd /d "%SCRIPT_DIR%"
|
|
||||||
)
|
|
||||||
echo.
|
|
||||||
|
|
||||||
:: 启动前端 App
|
|
||||||
echo %BLUE%🔄 启动前端 App 服务...%RESET%
|
|
||||||
if exist "%APP_PID_FILE%" (
|
|
||||||
set /p PID=<"%APP_PID_FILE%"
|
|
||||||
tasklist /FI "PID eq %PID%" 2>NUL | find /I "%PID%" >NUL
|
|
||||||
if %errorlevel%==0 (
|
|
||||||
echo %YELLOW%⚠️ 前端 App 服务已在运行 (PID: %PID%)%RESET%
|
|
||||||
) else (
|
|
||||||
echo %GREEN%✅ 启动前端 App 服务...%RESET%
|
|
||||||
cd /d "%APP_DIR%"
|
|
||||||
start "FastapiAdmin App" /B pnpm dev:h5 > "%APP_LOG%" 2>&1
|
|
||||||
:: 获取新启动进程的 PID
|
|
||||||
for /f "tokens=2" %%a in ('tasklist /FI "WINDOWTITLE eq FastapiAdmin App" /NH') do (
|
|
||||||
echo %%a > "%APP_PID_FILE%"
|
|
||||||
echo %GREEN%✅ 前端 App 服务已启动 (PID: %%a)%RESET%
|
|
||||||
)
|
|
||||||
cd /d "%SCRIPT_DIR%"
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
echo %GREEN%✅ 启动前端 App 服务...%RESET%
|
|
||||||
cd /d "%APP_DIR%"
|
|
||||||
start "FastapiAdmin App" /B pnpm dev:h5 > "%APP_LOG%" 2>&1
|
|
||||||
:: 获取新启动进程的 PID
|
|
||||||
for /f "tokens=2" %%a in ('tasklist /FI "WINDOWTITLE eq FastapiAdmin App" /NH') do (
|
|
||||||
echo %%a > "%APP_PID_FILE%"
|
|
||||||
echo %GREEN%✅ 前端 App 服务已启动 (PID: %%a)%RESET%
|
|
||||||
)
|
|
||||||
cd /d "%SCRIPT_DIR%"
|
|
||||||
)
|
|
||||||
echo.
|
|
||||||
|
|
||||||
:: 启动文档服务
|
|
||||||
echo %BLUE%🔄 启动文档服务...%RESET%
|
|
||||||
if exist "%DOCS_PID_FILE%" (
|
|
||||||
set /p PID=<"%DOCS_PID_FILE%"
|
|
||||||
tasklist /FI "PID eq %PID%" 2>NUL | find /I "%PID%" >NUL
|
|
||||||
if %errorlevel%==0 (
|
|
||||||
echo %YELLOW%⚠️ 文档服务已在运行 (PID: %PID%)%RESET%
|
|
||||||
) else (
|
|
||||||
echo %GREEN%✅ 启动文档服务...%RESET%
|
|
||||||
cd /d "%DOCS_DIR%"
|
|
||||||
start "FastapiAdmin Docs" /B pnpm dev > "%DOCS_LOG%" 2>&1
|
|
||||||
:: 获取新启动进程的 PID
|
|
||||||
for /f "tokens=2" %%a in ('tasklist /FI "WINDOWTITLE eq FastapiAdmin Docs" /NH') do (
|
|
||||||
echo %%a > "%DOCS_PID_FILE%"
|
|
||||||
echo %GREEN%✅ 文档服务已启动 (PID: %%a)%RESET%
|
|
||||||
)
|
|
||||||
cd /d "%SCRIPT_DIR%"
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
echo %GREEN%✅ 启动文档服务...%RESET%
|
|
||||||
cd /d "%DOCS_DIR%"
|
|
||||||
start "FastapiAdmin Docs" /B pnpm dev > "%DOCS_LOG%" 2>&1
|
|
||||||
:: 获取新启动进程的 PID
|
|
||||||
for /f "tokens=2" %%a in ('tasklist /FI "WINDOWTITLE eq FastapiAdmin Docs" /NH') do (
|
|
||||||
echo %%a > "%DOCS_PID_FILE%"
|
|
||||||
echo %GREEN%✅ 文档服务已启动 (PID: %%a)%RESET%
|
|
||||||
)
|
|
||||||
cd /d "%SCRIPT_DIR%"
|
|
||||||
)
|
|
||||||
echo.
|
echo.
|
||||||
echo %GREEN%🎉 所有服务启动完成!%RESET%
|
echo %GREEN%🎉 所有服务启动完成!%RESET%
|
||||||
echo.
|
echo.
|
||||||
echo %BLUE%访问地址:%RESET%
|
echo %BLUE%访问地址:%RESET%
|
||||||
echo %GREEN% 🚀 后端 API: http://localhost:8001/api/v1/docs%RESET%
|
echo 🚀 后端 API: http://localhost:8001/api/v1/docs
|
||||||
echo %GREEN% 🌐 前端 Web: http://localhost:5173%RESET%
|
echo 🌐 前端 Web: http://localhost:5173
|
||||||
echo %GREEN% 📱 前端 App: http://localhost:8080%RESET%
|
echo 📱 前端 App: http://localhost:8080
|
||||||
echo %GREEN% 📚 文档服务: http://localhost:5174%RESET%
|
echo 📚 文档服务: http://localhost:5174
|
||||||
echo.
|
echo.
|
||||||
goto :end
|
goto :end
|
||||||
|
|
||||||
|
:start_service
|
||||||
|
set "NAME=%~1"
|
||||||
|
set "DIR=%~2"
|
||||||
|
set "CMD=%~3"
|
||||||
|
set "TITLE=%~4"
|
||||||
|
|
||||||
|
echo %BLUE%🔄 启动 %NAME% 服务...%RESET%
|
||||||
|
set "PID_FILE=%LOG_DIR%\%NAME%.pid"
|
||||||
|
set "LOG_FILE=%LOG_DIR%\%NAME%.log"
|
||||||
|
|
||||||
|
if exist "%PID_FILE%" (
|
||||||
|
set /p OLD_PID=<"%PID_FILE%"
|
||||||
|
tasklist /FI "PID eq !OLD_PID!" 2>NUL | findstr /I "!OLD_PID!" >NUL
|
||||||
|
if !errorlevel!==0 (
|
||||||
|
echo %YELLOW%⚠️ %NAME% 已在运行 (PID: !OLD_PID!)%RESET%
|
||||||
|
exit /b
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
cd /d "%DIR%"
|
||||||
|
start "%TITLE%" /B %CMD% > "%LOG_FILE%" 2>&1
|
||||||
|
:: 通过窗口标题获取准确 PID
|
||||||
|
for /f "tokens=2 delims= " %%a in ('tasklist /FI "WINDOWTITLE eq %TITLE%" /NH 2^>NUL') do (
|
||||||
|
echo %%a > "%PID_FILE%"
|
||||||
|
)
|
||||||
|
echo %GREEN%✅ %NAME% 已启动 (日志: %LOG_FILE%)%RESET%
|
||||||
|
cd /d "%SCRIPT_DIR%"
|
||||||
|
exit /b
|
||||||
|
|
||||||
|
:: ==================== 停止 ====================
|
||||||
:stop_all
|
:stop_all
|
||||||
echo %CYAN%⏹️ 开始停止所有服务...%RESET%
|
echo %CYAN%⏹️ 开始停止所有服务...%RESET%
|
||||||
echo.
|
echo.
|
||||||
|
call :stop_service "backend"
|
||||||
:: 停止后端
|
call :stop_service "frontend"
|
||||||
if exist "%BACKEND_PID_FILE%" (
|
call :stop_service "app"
|
||||||
set /p PID=<"%BACKEND_PID_FILE%"
|
call :stop_service "docs"
|
||||||
tasklist /FI "PID eq %PID%" 2>NUL | find /I "%PID%" >NUL
|
|
||||||
if %errorlevel%==0 (
|
|
||||||
echo %GREEN%✅ 停止后端服务 (PID: %PID%)%RESET%
|
|
||||||
taskkill /PID %PID% /F >NUL 2>&1
|
|
||||||
del "%BACKEND_PID_FILE%" >NUL 2>&1
|
|
||||||
) else (
|
|
||||||
echo %YELLOW%⚠️ 后端服务未在运行%RESET%
|
|
||||||
del "%BACKEND_PID_FILE%" >NUL 2>&1
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
echo %YELLOW%⚠️ 后端服务未在运行%RESET%
|
|
||||||
)
|
|
||||||
echo.
|
|
||||||
|
|
||||||
:: 停止前端 Web
|
|
||||||
if exist "%WEB_PID_FILE%" (
|
|
||||||
set /p PID=<"%WEB_PID_FILE%"
|
|
||||||
tasklist /FI "PID eq %PID%" 2>NUL | find /I "%PID%" >NUL
|
|
||||||
if %errorlevel%==0 (
|
|
||||||
echo %GREEN%✅ 停止前端 Web 服务 (PID: %PID%)%RESET%
|
|
||||||
taskkill /PID %PID% /F >NUL 2>&1
|
|
||||||
del "%WEB_PID_FILE%" >NUL 2>&1
|
|
||||||
) else (
|
|
||||||
echo %YELLOW%⚠️ 前端 Web 服务未在运行%RESET%
|
|
||||||
del "%WEB_PID_FILE%" >NUL 2>&1
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
echo %YELLOW%⚠️ 前端 Web 服务未在运行%RESET%
|
|
||||||
)
|
|
||||||
echo.
|
|
||||||
|
|
||||||
:: 停止前端 App
|
|
||||||
if exist "%APP_PID_FILE%" (
|
|
||||||
set /p PID=<"%APP_PID_FILE%"
|
|
||||||
tasklist /FI "PID eq %PID%" 2>NUL | find /I "%PID%" >NUL
|
|
||||||
if %errorlevel%==0 (
|
|
||||||
echo %GREEN%✅ 停止前端 App 服务 (PID: %PID%)%RESET%
|
|
||||||
taskkill /PID %PID% /F >NUL 2>&1
|
|
||||||
del "%APP_PID_FILE%" >NUL 2>&1
|
|
||||||
) else (
|
|
||||||
echo %YELLOW%⚠️ 前端 App 服务未在运行%RESET%
|
|
||||||
del "%APP_PID_FILE%" >NUL 2>&1
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
echo %YELLOW%⚠️ 前端 App 服务未在运行%RESET%
|
|
||||||
)
|
|
||||||
echo.
|
|
||||||
|
|
||||||
:: 停止文档服务
|
|
||||||
if exist "%DOCS_PID_FILE%" (
|
|
||||||
set /p PID=<"%DOCS_PID_FILE%"
|
|
||||||
tasklist /FI "PID eq %PID%" 2>NUL | find /I "%PID%" >NUL
|
|
||||||
if %errorlevel%==0 (
|
|
||||||
echo %GREEN%✅ 停止文档服务 (PID: %PID%)%RESET%
|
|
||||||
taskkill /PID %PID% /F >NUL 2>&1
|
|
||||||
del "%DOCS_PID_FILE%" >NUL 2>&1
|
|
||||||
) else (
|
|
||||||
echo %YELLOW%⚠️ 文档服务未在运行%RESET%
|
|
||||||
del "%DOCS_PID_FILE%" >NUL 2>&1
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
echo %YELLOW%⚠️ 文档服务未在运行%RESET%
|
|
||||||
)
|
|
||||||
echo.
|
|
||||||
echo %GREEN%🎉 所有服务已停止!%RESET%
|
echo %GREEN%🎉 所有服务已停止!%RESET%
|
||||||
echo.
|
echo.
|
||||||
goto :end
|
goto :end
|
||||||
|
|
||||||
:restart_all
|
:stop_service
|
||||||
echo %CYAN%🔄 开始重启所有服务...%RESET%
|
set "NAME=%~1"
|
||||||
echo.
|
set "PID_FILE=%LOG_DIR%\%NAME%.pid"
|
||||||
goto :stop_all
|
|
||||||
:restart_all_continue
|
|
||||||
echo.
|
|
||||||
goto :start_all
|
|
||||||
|
|
||||||
|
if exist "%PID_FILE%" (
|
||||||
|
set /p PID=<"%PID_FILE%"
|
||||||
|
tasklist /FI "PID eq !PID!" 2>NUL | findstr /I "!PID!" >NUL
|
||||||
|
if !errorlevel!==0 (
|
||||||
|
taskkill /PID !PID! /F >NUL 2>&1
|
||||||
|
echo %GREEN%✅ 已停止 %NAME% (PID: !PID!)%RESET%
|
||||||
|
)
|
||||||
|
del "%PID_FILE%" 2>NUL
|
||||||
|
) else (
|
||||||
|
echo %YELLOW%⚠️ %NAME% 未在运行%RESET%
|
||||||
|
)
|
||||||
|
exit /b
|
||||||
|
|
||||||
|
:: ==================== 重启 ====================
|
||||||
|
:restart_all
|
||||||
|
echo %CYAN%🔄 重启所有服务...%RESET%
|
||||||
|
call :stop_all
|
||||||
|
echo.
|
||||||
|
call :start_all
|
||||||
|
goto :end
|
||||||
|
|
||||||
|
:: ==================== 状态 ====================
|
||||||
:check_status
|
:check_status
|
||||||
echo %CYAN%🔍 检查服务状态...%RESET%
|
echo %CYAN%🔍 检查服务状态...%RESET%
|
||||||
echo.
|
echo.
|
||||||
|
call :check_service "backend"
|
||||||
:: 检查后端
|
call :check_service "frontend"
|
||||||
if exist "%BACKEND_PID_FILE%" (
|
call :check_service "app"
|
||||||
set /p PID=<"%BACKEND_PID_FILE%"
|
call :check_service "docs"
|
||||||
tasklist /FI "PID eq %PID%" 2>NUL | find /I "%PID%" >NUL
|
|
||||||
if %errorlevel%==0 (
|
|
||||||
echo %GREEN%✅ 后端服务: 运行中 (PID: %PID%)%RESET%
|
|
||||||
) else (
|
|
||||||
echo %RED%❌ 后端服务: 已停止%RESET%
|
|
||||||
del "%BACKEND_PID_FILE%" >NUL 2>&1
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
echo %RED%❌ 后端服务: 未启动%RESET%
|
|
||||||
)
|
|
||||||
|
|
||||||
:: 检查前端 Web
|
|
||||||
if exist "%WEB_PID_FILE%" (
|
|
||||||
set /p PID=<"%WEB_PID_FILE%"
|
|
||||||
tasklist /FI "PID eq %PID%" 2>NUL | find /I "%PID%" >NUL
|
|
||||||
if %errorlevel%==0 (
|
|
||||||
echo %GREEN%✅ 前端 Web: 运行中 (PID: %PID%)%RESET%
|
|
||||||
) else (
|
|
||||||
echo %RED%❌ 前端 Web: 已停止%RESET%
|
|
||||||
del "%WEB_PID_FILE%" >NUL 2>&1
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
echo %RED%❌ 前端 Web: 未启动%RESET%
|
|
||||||
)
|
|
||||||
|
|
||||||
:: 检查前端 App
|
|
||||||
if exist "%APP_PID_FILE%" (
|
|
||||||
set /p PID=<"%APP_PID_FILE%"
|
|
||||||
tasklist /FI "PID eq %PID%" 2>NUL | find /I "%PID%" >NUL
|
|
||||||
if %errorlevel%==0 (
|
|
||||||
echo %GREEN%✅ 前端 App: 运行中 (PID: %PID%)%RESET%
|
|
||||||
) else (
|
|
||||||
echo %RED%❌ 前端 App: 已停止%RESET%
|
|
||||||
del "%APP_PID_FILE%" >NUL 2>&1
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
echo %RED%❌ 前端 App: 未启动%RESET%
|
|
||||||
)
|
|
||||||
|
|
||||||
:: 检查文档服务
|
|
||||||
if exist "%DOCS_PID_FILE%" (
|
|
||||||
set /p PID=<"%DOCS_PID_FILE%"
|
|
||||||
tasklist /FI "PID eq %PID%" 2>NUL | find /I "%PID%" >NUL
|
|
||||||
if %errorlevel%==0 (
|
|
||||||
echo %GREEN%✅ 文档服务: 运行中 (PID: %PID%)%RESET%
|
|
||||||
) else (
|
|
||||||
echo %RED%❌ 文档服务: 已停止%RESET%
|
|
||||||
del "%DOCS_PID_FILE%" >NUL 2>&1
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
echo %RED%❌ 文档服务: 未启动%RESET%
|
|
||||||
)
|
|
||||||
echo.
|
|
||||||
goto :end
|
goto :end
|
||||||
|
|
||||||
|
:check_service
|
||||||
|
set "NAME=%~1"
|
||||||
|
set "PID_FILE=%LOG_DIR%\%NAME%.pid"
|
||||||
|
|
||||||
|
if exist "%PID_FILE%" (
|
||||||
|
set /p PID=<"%PID_FILE%"
|
||||||
|
tasklist /FI "PID eq !PID!" 2>NUL | findstr /I "!PID!" >NUL
|
||||||
|
if !errorlevel!==0 (
|
||||||
|
echo %GREEN%✅ %NAME%: 运行中 (PID: !PID!)%RESET%
|
||||||
|
) else (
|
||||||
|
echo %RED%❌ %NAME%: 已停止%RESET%
|
||||||
|
del "%PID_FILE%" 2>NUL
|
||||||
|
)
|
||||||
|
) else (
|
||||||
|
echo %RED%❌ %NAME%: 未启动%RESET%
|
||||||
|
)
|
||||||
|
exit /b
|
||||||
|
|
||||||
|
:: ==================== 结束 ====================
|
||||||
:end
|
:end
|
||||||
echo %GREEN%========================================%RESET%
|
echo %GREEN%========================================%RESET%
|
||||||
echo %GREEN%📦 FastapiAdmin 启动脚本%RESET%
|
|
||||||
echo %GREEN%========================================%RESET%
|
|
||||||
endlocal
|
endlocal
|
||||||
|
Before Width: | Height: | Size: 384 KiB After Width: | Height: | Size: 721 KiB |
|
Before Width: | Height: | Size: 608 KiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 697 KiB After Width: | Height: | Size: 1013 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 752 KiB After Width: | Height: | Size: 752 KiB |
@@ -10,12 +10,12 @@ VITE_APP_TITLE = FastAPI Admin
|
|||||||
VITE_API_URL = /
|
VITE_API_URL = /
|
||||||
|
|
||||||
# 代理目标:本机后端(若出现 ENOTFOUND,说明上面域名在当前网络/DNS 下不可解析,改用本地或可用地址)
|
# 代理目标:本机后端(若出现 ENOTFOUND,说明上面域名在当前网络/DNS 下不可解析,改用本地或可用地址)
|
||||||
VITE_API_BASE_URL = http://127.0.0.1:8001
|
# VITE_API_BASE_URL = http://127.0.0.1:8001
|
||||||
# VITE_API_BASE_URL = https://service.fastapiadmin.com
|
VITE_API_BASE_URL = https://service.fastapiadmin.com
|
||||||
|
|
||||||
# 是否删除控制台输出
|
# 是否删除控制台输出
|
||||||
VITE_DROP_CONSOLE = false
|
VITE_DROP_CONSOLE = false
|
||||||
|
|
||||||
# WebSocket 端点(AI对话功能需要配置)
|
# WebSocket 端点(AI对话功能需要配置)
|
||||||
VITE_APP_WS_ENDPOINT = ws://localhost:8001
|
# VITE_APP_WS_ENDPOINT = ws://localhost:8001
|
||||||
# VITE_APP_WS_ENDPOINT = wss://service.fastapiadmin.com
|
VITE_APP_WS_ENDPOINT = wss://service.fastapiadmin.com
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ VITE_APP_ENV = production
|
|||||||
VITE_APP_TITLE = FastAPI Admin
|
VITE_APP_TITLE = FastAPI Admin
|
||||||
|
|
||||||
# API 请求基础路径(生产环境使用完整后端地址)
|
# API 请求基础路径(生产环境使用完整后端地址)
|
||||||
VITE_API_BASE_URL = https://server.fastapiadmin.com
|
VITE_API_BASE_URL = https://service.fastapiadmin.com
|
||||||
|
|
||||||
# 是否删除控制台输出
|
# 是否删除控制台输出
|
||||||
VITE_DROP_CONSOLE = true
|
VITE_DROP_CONSOLE = true
|
||||||
|
|
||||||
# WebSocket 端点(AI对话功能需要配置,生产环境建议使用 wss)
|
# WebSocket 端点(AI对话功能需要配置,生产环境建议使用 wss)
|
||||||
VITE_APP_WS_ENDPOINT = wss://server.fastapiadmin.com
|
VITE_APP_WS_ENDPOINT = wss://service.fastapiadmin.com
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
.fa-table__main {
|
.fa-table__main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
// VueDraggable 透传高度,确保 ElTable height: 100% 能正确解析
|
// VueDraggable 透传高度,确保 ElTable height: 100% 能正确解析
|
||||||
> * {
|
> * {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const fastEnterConfig: FastEnterConfig = {
|
|||||||
iconColor: "#377dff",
|
iconColor: "#377dff",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
order: 1,
|
order: 1,
|
||||||
routeName: "DashboardConsole",
|
routeName: "DashboardWorkplace",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "分析页",
|
name: "分析页",
|
||||||
@@ -74,13 +74,13 @@ const fastEnterConfig: FastEnterConfig = {
|
|||||||
routeName: "ChangeLog",
|
routeName: "ChangeLog",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "哔哩哔哩",
|
name: "操作手册",
|
||||||
description: "技术分享与交流",
|
description: "产品操作指南",
|
||||||
icon: "ri:bilibili-line",
|
icon: "ri:book-2-line",
|
||||||
iconColor: "#FB7299",
|
iconColor: "#38C0FC",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
order: 8,
|
order: 8,
|
||||||
link: WEB_LINKS.BILIBILI,
|
routeName: "FastlinkTutorial",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// 快速链接
|
// 快速链接
|
||||||
@@ -107,7 +107,7 @@ const fastEnterConfig: FastEnterConfig = {
|
|||||||
name: "定价",
|
name: "定价",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
order: 4,
|
order: 4,
|
||||||
routeName: "DashboardPricing",
|
routeName: "FastlinkPricing",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "个人中心",
|
name: "个人中心",
|
||||||
@@ -119,7 +119,7 @@ const fastEnterConfig: FastEnterConfig = {
|
|||||||
name: "留言管理",
|
name: "留言管理",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
order: 6,
|
order: 6,
|
||||||
routeName: "ArticleList",
|
routeName: "FastlinkArticleList",
|
||||||
routeQuery: { commentWall: "1" },
|
routeQuery: { commentWall: "1" },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -545,10 +545,8 @@
|
|||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
"console": "Console",
|
|
||||||
"analysis": "Analysis",
|
"analysis": "Analysis",
|
||||||
"ecommerce": "Ecommerce",
|
"visualization": "3D Visualization",
|
||||||
"map": "Map",
|
|
||||||
"pricing": "Pricing",
|
"pricing": "Pricing",
|
||||||
"tutorial": "User manual"
|
"tutorial": "User manual"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -545,10 +545,8 @@
|
|||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "仪表盘",
|
"title": "仪表盘",
|
||||||
"console": "控制台",
|
|
||||||
"analysis": "分析页",
|
"analysis": "分析页",
|
||||||
"ecommerce": "电子商务",
|
"visualization": "三维可视化",
|
||||||
"map": "地图",
|
|
||||||
"pricing": "定价",
|
"pricing": "定价",
|
||||||
"tutorial": "操作手册"
|
"tutorial": "操作手册"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { RouterView, useRoute } from "vue-router";
|
|||||||
/** 首页 / 仪表盘父级 meta(侧栏、静态子路由共用) */
|
/** 首页 / 仪表盘父级 meta(侧栏、静态子路由共用) */
|
||||||
export const HOME_MENU_META: RouteMeta = {
|
export const HOME_MENU_META: RouteMeta = {
|
||||||
title: "menus.home.title",
|
title: "menus.home.title",
|
||||||
icon: "ri:presentation-line",
|
icon: "ri:home-smile-2-line",
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -149,21 +149,10 @@ export const dashboardLayoutChildren: AppRouteRecordRaw[] = [
|
|||||||
component: () => import("@views/dashboard/workplace/index.vue"),
|
component: () => import("@views/dashboard/workplace/index.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
title: "menus.workplace.title",
|
title: "menus.workplace.title",
|
||||||
icon: "ri:layout-grid-line",
|
icon: "ri:bar-chart-box-line",
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "console",
|
|
||||||
name: "DashboardConsole",
|
|
||||||
component: () => import("@views/dashboard/console/index.vue"),
|
|
||||||
meta: {
|
|
||||||
title: "menus.dashboard.console",
|
|
||||||
icon: "ri:home-smile-2-line",
|
|
||||||
keepAlive: false,
|
|
||||||
fixedTab: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "analysis",
|
path: "analysis",
|
||||||
name: "DashboardAnalysis",
|
name: "DashboardAnalysis",
|
||||||
@@ -174,73 +163,6 @@ export const dashboardLayoutChildren: AppRouteRecordRaw[] = [
|
|||||||
keepAlive: false,
|
keepAlive: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "ecommerce",
|
|
||||||
name: "DashboardEcommerce",
|
|
||||||
component: () => import("@views/dashboard/ecommerce/index.vue"),
|
|
||||||
meta: {
|
|
||||||
title: "menus.dashboard.ecommerce",
|
|
||||||
icon: "ri:bar-chart-box-line",
|
|
||||||
keepAlive: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "map",
|
|
||||||
name: "DashboardMap",
|
|
||||||
component: () => import("@views/dashboard/map/index.vue"),
|
|
||||||
meta: {
|
|
||||||
title: "menus.dashboard.map",
|
|
||||||
icon: "ri:map-pin-line",
|
|
||||||
keepAlive: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "pricing",
|
|
||||||
name: "DashboardPricing",
|
|
||||||
component: () => import("@views/dashboard/pricing/index.vue"),
|
|
||||||
meta: {
|
|
||||||
title: "menus.dashboard.pricing",
|
|
||||||
icon: "ri:money-cny-box-line",
|
|
||||||
keepAlive: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "article",
|
|
||||||
name: "DashboardArticle",
|
|
||||||
component: NestedRouterParent,
|
|
||||||
meta: {
|
|
||||||
title: "menus.article.title",
|
|
||||||
icon: "ri:book-2-line",
|
|
||||||
alwaysShow: true,
|
|
||||||
roles: ["R_SUPER", "R_ADMIN"],
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: "article-list",
|
|
||||||
name: "ArticleList",
|
|
||||||
component: () => import("@views/dashboard/article/list/index.vue"),
|
|
||||||
meta: {
|
|
||||||
title: "menus.article.articleList",
|
|
||||||
icon: "ri:article-line",
|
|
||||||
keepAlive: true,
|
|
||||||
authList: [
|
|
||||||
{ title: "新增", authMark: "add" },
|
|
||||||
{ title: "编辑", authMark: "edit" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "tutorial",
|
|
||||||
name: "DashboardTutorial",
|
|
||||||
component: () => import("@views/dashboard/tutorial/index.vue"),
|
|
||||||
meta: {
|
|
||||||
title: "menus.dashboard.tutorial",
|
|
||||||
icon: "ri:book-2-line",
|
|
||||||
keepAlive: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -432,27 +354,9 @@ export const staticRoutes: AppRouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: "home",
|
path: "home",
|
||||||
name: HOME_ROUTE_NAME,
|
name: HOME_ROUTE_NAME,
|
||||||
component: () => import("@views/dashboard/index.vue"),
|
component: () => import("@views/dashboard/home/index.vue"),
|
||||||
meta: HOME_MENU_META,
|
meta: HOME_MENU_META,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "profile",
|
|
||||||
name: "Profile",
|
|
||||||
meta: { title: "个人中心", icon: "ri:user-line", hidden: true },
|
|
||||||
component: () => import("@views/current/profile.vue"),
|
|
||||||
},
|
|
||||||
/** 更新日志(mock 数据):侧栏隐藏,由顶栏头像下拉进入(routeName 供 fastEnter 等使用) */
|
|
||||||
{
|
|
||||||
path: "changelog",
|
|
||||||
name: "ChangeLog",
|
|
||||||
meta: {
|
|
||||||
title: "menus.changelog.title",
|
|
||||||
icon: "ri:draft-line",
|
|
||||||
hidden: true,
|
|
||||||
keepAlive: true,
|
|
||||||
},
|
|
||||||
component: () => import("@views/changelog/index.vue"),
|
|
||||||
},
|
|
||||||
/** 仪表盘子路由定义见同文件导出的 `dashboardLayoutChildren` */
|
/** 仪表盘子路由定义见同文件导出的 `dashboardLayoutChildren` */
|
||||||
{
|
{
|
||||||
path: "dashboard",
|
path: "dashboard",
|
||||||
@@ -462,6 +366,65 @@ export const staticRoutes: AppRouteRecordRaw[] = [
|
|||||||
meta: DASHBOARD_PARENT_META,
|
meta: DASHBOARD_PARENT_META,
|
||||||
children: dashboardLayoutChildren,
|
children: dashboardLayoutChildren,
|
||||||
},
|
},
|
||||||
|
/** 快速链接:统一父级,不在菜单中展示,通过 URL 或 fastEnter 直接访问 */
|
||||||
|
{
|
||||||
|
path: "fastlink",
|
||||||
|
name: "Fastlink",
|
||||||
|
component: NestedRouterParent,
|
||||||
|
meta: { hidden: true },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "profile",
|
||||||
|
name: "FastlinkProfile",
|
||||||
|
meta: { title: "个人中心", icon: "ri:user-line", hidden: true },
|
||||||
|
component: () => import("@views/fastlink/current/profile.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "changelog",
|
||||||
|
name: "FastlinkChangeLog",
|
||||||
|
meta: {
|
||||||
|
title: "menus.changelog.title",
|
||||||
|
icon: "ri:draft-line",
|
||||||
|
hidden: true,
|
||||||
|
keepAlive: true,
|
||||||
|
},
|
||||||
|
component: () => import("@views/fastlink/changelog/index.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "pricing",
|
||||||
|
name: "FastlinkPricing",
|
||||||
|
meta: {
|
||||||
|
title: "menus.dashboard.pricing",
|
||||||
|
icon: "ri:money-cny-box-line",
|
||||||
|
hidden: true,
|
||||||
|
keepAlive: true,
|
||||||
|
},
|
||||||
|
component: () => import("@views/fastlink/pricing/index.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "article/list",
|
||||||
|
name: "FastlinkArticleList",
|
||||||
|
meta: {
|
||||||
|
title: "menus.article.articleList",
|
||||||
|
icon: "ri:article-line",
|
||||||
|
hidden: true,
|
||||||
|
keepAlive: true,
|
||||||
|
},
|
||||||
|
component: () => import("@views/fastlink/article/index.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "tutorial",
|
||||||
|
name: "FastlinkTutorial",
|
||||||
|
meta: {
|
||||||
|
title: "menus.dashboard.tutorial",
|
||||||
|
icon: "ri:book-2-line",
|
||||||
|
hidden: true,
|
||||||
|
keepAlive: true,
|
||||||
|
},
|
||||||
|
component: () => import("@views/fastlink/tutorial/index.vue"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -254,8 +254,8 @@ export const useUserStore = defineStore(
|
|||||||
(await getRouterUtils()).resetRouteInitState();
|
(await getRouterUtils()).resetRouteInitState();
|
||||||
Auth.setTokens(data.access_token, data.refresh_token, rememberMe.value);
|
Auth.setTokens(data.access_token, data.refresh_token, rememberMe.value);
|
||||||
setToken(data.access_token, data.refresh_token);
|
setToken(data.access_token, data.refresh_token);
|
||||||
setLoginStatus(true);
|
|
||||||
await getUserInfo();
|
await getUserInfo();
|
||||||
|
setLoginStatus(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -34,283 +34,281 @@
|
|||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
|
|
||||||
<!-- —— 图表组件演示 —— -->
|
<ElRow :gutter="20">
|
||||||
<div class="analysis-charts-showcase border-g-200">
|
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
||||||
<div class="py-5">
|
<div class="fa-card">
|
||||||
<h2 class="page-title">图表</h2>
|
<div class="pb-3.5">
|
||||||
<ElRow :gutter="20">
|
<span class="text-base font-medium">柱状图(单数据)</span>
|
||||||
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
</div>
|
||||||
<div class="fa-card">
|
<FaBarChart
|
||||||
<div class="pb-3.5">
|
:data="singleBarData"
|
||||||
<span class="text-base font-medium">柱状图(单数据)</span>
|
:xAxisData="xAxisData"
|
||||||
</div>
|
:showLegend="true"
|
||||||
<FaBarChart
|
legendPosition="right"
|
||||||
:data="singleBarData"
|
/>
|
||||||
:xAxisData="xAxisData"
|
</div>
|
||||||
:showLegend="true"
|
</ElCol>
|
||||||
legendPosition="right"
|
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
||||||
/>
|
<div class="fa-card">
|
||||||
</div>
|
<div class="pb-3.5">
|
||||||
</ElCol>
|
<span class="text-base font-medium">柱状图(多组数据)</span>
|
||||||
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
</div>
|
||||||
<div class="fa-card">
|
<FaBarChart
|
||||||
<div class="pb-3.5">
|
:data="multiBarData"
|
||||||
<span class="text-base font-medium">柱状图(多组数据)</span>
|
:xAxisData="xAxisData"
|
||||||
</div>
|
:showLegend="true"
|
||||||
<FaBarChart
|
barWidth="26%"
|
||||||
:data="multiBarData"
|
/>
|
||||||
:xAxisData="xAxisData"
|
</div>
|
||||||
:showLegend="true"
|
</ElCol>
|
||||||
barWidth="26%"
|
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
||||||
/>
|
<div class="fa-card">
|
||||||
</div>
|
<div class="pb-3.5">
|
||||||
</ElCol>
|
<span class="text-base font-medium">柱状图(堆叠)</span>
|
||||||
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
</div>
|
||||||
<div class="fa-card">
|
<FaBarChart
|
||||||
<div class="pb-3.5">
|
:data="stackBarData"
|
||||||
<span class="text-base font-medium">柱状图(堆叠)</span>
|
:xAxisData="stackXAxisData"
|
||||||
</div>
|
:showLegend="true"
|
||||||
<FaBarChart
|
:stack="true"
|
||||||
:data="stackBarData"
|
barWidth="26%"
|
||||||
:xAxisData="stackXAxisData"
|
/>
|
||||||
:showLegend="true"
|
</div>
|
||||||
:stack="true"
|
</ElCol>
|
||||||
barWidth="26%"
|
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
||||||
/>
|
<div class="fa-card">
|
||||||
</div>
|
<div class="pb-3.5">
|
||||||
</ElCol>
|
<span class="text-base font-medium">折线图</span>
|
||||||
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
</div>
|
||||||
<div class="fa-card">
|
<FaLineChart
|
||||||
<div class="pb-3.5">
|
:data="[58, 15, 82, 35, 120, 62, 45]"
|
||||||
<span class="text-base font-medium">折线图</span>
|
:xAxisData="['一月', '二月', '三月', '四月', '五月', '六月', '七月']"
|
||||||
</div>
|
symbol="none"
|
||||||
<FaLineChart
|
:symbolSize="7"
|
||||||
:data="[58, 15, 82, 35, 120, 62, 45]"
|
/>
|
||||||
:xAxisData="['一月', '二月', '三月', '四月', '五月', '六月', '七月']"
|
</div>
|
||||||
symbol="none"
|
</ElCol>
|
||||||
:symbolSize="7"
|
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
||||||
/>
|
<div class="fa-card">
|
||||||
</div>
|
<div class="pb-3.5">
|
||||||
</ElCol>
|
<span class="text-base font-medium">折线图(渐变背景)</span>
|
||||||
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
</div>
|
||||||
<div class="fa-card">
|
<FaLineChart
|
||||||
<div class="pb-3.5">
|
:data="[28, 45, 82, 35, 100, 32, 55]"
|
||||||
<span class="text-base font-medium">折线图(渐变背景)</span>
|
:xAxisData="['一月', '二月', '三月', '四月', '五月', '六月', '七月']"
|
||||||
</div>
|
:showAreaColor="true"
|
||||||
<FaLineChart
|
/>
|
||||||
:data="[28, 45, 82, 35, 100, 32, 55]"
|
</div>
|
||||||
:xAxisData="['一月', '二月', '三月', '四月', '五月', '六月', '七月']"
|
</ElCol>
|
||||||
:showAreaColor="true"
|
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
||||||
/>
|
<div class="fa-card">
|
||||||
</div>
|
<div class="pb-3.5">
|
||||||
</ElCol>
|
<span class="text-base font-medium">折线图(多组数据)</span>
|
||||||
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
</div>
|
||||||
<div class="fa-card">
|
<FaLineChart
|
||||||
<div class="pb-3.5">
|
:data="multiLineData"
|
||||||
<span class="text-base font-medium">折线图(多组数据)</span>
|
:xAxisData="['1月', '2月', '3月', '4月', '5月', '6月']"
|
||||||
</div>
|
:showLegend="true"
|
||||||
<FaLineChart
|
/>
|
||||||
:data="multiLineData"
|
</div>
|
||||||
:xAxisData="['1月', '2月', '3月', '4月', '5月', '6月']"
|
</ElCol>
|
||||||
:showLegend="true"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ElCol>
|
|
||||||
|
|
||||||
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
||||||
<div class="fa-card">
|
<div class="fa-card">
|
||||||
<div class="pb-3.5">
|
<div class="pb-3.5">
|
||||||
<span class="text-base font-medium">柱状图(水平)</span>
|
<span class="text-base font-medium">柱状图(水平)</span>
|
||||||
</div>
|
</div>
|
||||||
<FaHBarChart
|
<FaHBarChart
|
||||||
:data="[50, 80, 120, 90, 60]"
|
:data="[50, 80, 120, 90, 60]"
|
||||||
:xAxisData="['产品A', '产品B', '产品C', '产品D', '产品E']"
|
:xAxisData="['产品A', '产品B', '产品C', '产品D', '产品E']"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
||||||
<div class="fa-card">
|
<div class="fa-card">
|
||||||
<div class="pb-3.5">
|
<div class="pb-3.5">
|
||||||
<span class="text-base font-medium">柱状图(水平)</span>
|
<span class="text-base font-medium">柱状图(水平)</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FaHBarChart
|
<FaHBarChart
|
||||||
:data="[
|
:data="[
|
||||||
{ name: '系列1', data: [10, 20, 30] },
|
{ name: '系列1', data: [10, 20, 30] },
|
||||||
{ name: '系列2', data: [15, 25, 35] },
|
{ name: '系列2', data: [15, 25, 35] },
|
||||||
]"
|
]"
|
||||||
:xAxisData="['类目1', '类目2', '类目3']"
|
:xAxisData="['类目1', '类目2', '类目3']"
|
||||||
:showLegend="true"
|
:showLegend="true"
|
||||||
barWidth="30%"
|
barWidth="30%"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
||||||
<div class="fa-card">
|
<div class="fa-card">
|
||||||
<div class="pb-3.5">
|
<div class="pb-3.5">
|
||||||
<span class="text-base font-medium">柱状图(水平堆叠)</span>
|
<span class="text-base font-medium">柱状图(水平堆叠)</span>
|
||||||
</div>
|
</div>
|
||||||
<FaHBarChart
|
<FaHBarChart
|
||||||
:data="[
|
:data="[
|
||||||
{ name: '系列1', data: [10, 20, 30] },
|
{ name: '系列1', data: [10, 20, 30] },
|
||||||
{ name: '系列2', data: [15, 25, 35] },
|
{ name: '系列2', data: [15, 25, 35] },
|
||||||
]"
|
]"
|
||||||
:xAxisData="['类目1', '类目2', '类目3']"
|
:xAxisData="['类目1', '类目2', '类目3']"
|
||||||
:showLegend="true"
|
:showLegend="true"
|
||||||
:stack="true"
|
:stack="true"
|
||||||
barWidth="30%"
|
barWidth="30%"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
|
|
||||||
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
||||||
<div class="fa-card">
|
<div class="fa-card">
|
||||||
<div class="pb-3.5">
|
<div class="pb-3.5">
|
||||||
<span class="text-base font-medium">环形图</span>
|
<span class="text-base font-medium">环形图</span>
|
||||||
</div>
|
</div>
|
||||||
<FaRingChart
|
<FaRingChart
|
||||||
:data="[
|
:data="[
|
||||||
{ value: 35, name: '分类A' },
|
{ value: 35, name: '分类A' },
|
||||||
{ value: 28, name: '分类B' },
|
{ value: 28, name: '分类B' },
|
||||||
{ value: 42, name: '分类C' },
|
{ value: 42, name: '分类C' },
|
||||||
]"
|
]"
|
||||||
:radius="['54%', '70%']"
|
:radius="['54%', '70%']"
|
||||||
legendPosition="bottom"
|
legendPosition="bottom"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
|
|
||||||
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
||||||
<div class="fa-card">
|
<div class="fa-card">
|
||||||
<div class="pb-3.5">
|
<div class="pb-3.5">
|
||||||
<span class="text-base font-medium">环形图</span>
|
<span class="text-base font-medium">环形图</span>
|
||||||
</div>
|
</div>
|
||||||
<FaRingChart
|
<FaRingChart
|
||||||
:data="[
|
:data="[
|
||||||
{ value: 35, name: '分类A' },
|
{ value: 35, name: '分类A' },
|
||||||
{ value: 28, name: '分类B' },
|
{ value: 28, name: '分类B' },
|
||||||
{ value: 42, name: '分类C' },
|
{ value: 42, name: '分类C' },
|
||||||
{ value: 32, name: '分类D' },
|
{ value: 32, name: '分类D' },
|
||||||
{ value: 26, name: '分类E' },
|
{ value: 26, name: '分类E' },
|
||||||
{ value: 37, name: '分类F' },
|
{ value: 37, name: '分类F' },
|
||||||
]"
|
]"
|
||||||
:radius="['54%', '70%']"
|
:radius="['54%', '70%']"
|
||||||
:borderRadius="0"
|
:borderRadius="0"
|
||||||
:showLegend="true"
|
:showLegend="true"
|
||||||
legendPosition="bottom"
|
legendPosition="bottom"
|
||||||
centerText="¥300,458"
|
centerText="¥300,458"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
||||||
<div class="fa-card">
|
<div class="fa-card">
|
||||||
<div class="pb-3.5">
|
<div class="pb-3.5">
|
||||||
<span class="text-base font-medium">饼图</span>
|
<span class="text-base font-medium">饼图</span>
|
||||||
</div>
|
</div>
|
||||||
<FaRingChart
|
<FaRingChart
|
||||||
:data="[
|
:data="[
|
||||||
{ value: 30, name: '分类A' },
|
{ value: 30, name: '分类A' },
|
||||||
{ value: 25, name: '分类B' },
|
{ value: 25, name: '分类B' },
|
||||||
{ value: 45, name: '分类C' },
|
{ value: 45, name: '分类C' },
|
||||||
]"
|
]"
|
||||||
:radius="['0%', '70%']"
|
:radius="['0%', '70%']"
|
||||||
:showLegend="true"
|
:showLegend="true"
|
||||||
legendPosition="right"
|
legendPosition="right"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
||||||
<div class="fa-card">
|
<div class="fa-card">
|
||||||
<div class="pb-3.5">
|
<div class="pb-3.5">
|
||||||
<span class="text-base font-medium">散点图</span>
|
<span class="text-base font-medium">散点图</span>
|
||||||
</div>
|
</div>
|
||||||
<FaScatterChart
|
<FaScatterChart
|
||||||
:data="[
|
:data="[
|
||||||
{ value: [1, 3] },
|
{ value: [1, 3] },
|
||||||
{ value: [2, 4] },
|
{ value: [2, 4] },
|
||||||
{ value: [3, 5] },
|
{ value: [3, 5] },
|
||||||
{ value: [4, 6] },
|
{ value: [4, 6] },
|
||||||
{ value: [5, 7] },
|
{ value: [5, 7] },
|
||||||
{ value: [6, 8] },
|
{ value: [6, 8] },
|
||||||
{ value: [7, 7] },
|
{ value: [7, 7] },
|
||||||
{ value: [8, 9] },
|
{ value: [8, 9] },
|
||||||
{ value: [9, 8] },
|
{ value: [9, 8] },
|
||||||
{ value: [10, 6] },
|
{ value: [10, 6] },
|
||||||
{ value: [11, 7] },
|
{ value: [11, 7] },
|
||||||
{ value: [12, 8] },
|
{ value: [12, 8] },
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
|
|
||||||
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
||||||
<div class="fa-card">
|
<div class="fa-card">
|
||||||
<div class="pb-3.5">
|
<div class="pb-3.5">
|
||||||
<span class="text-base font-medium">雷达图</span>
|
<span class="text-base font-medium">雷达图</span>
|
||||||
</div>
|
</div>
|
||||||
<FaRadarChart
|
<FaRadarChart
|
||||||
:indicator="[
|
:indicator="[
|
||||||
{ name: '销售', max: 100 },
|
{ name: '销售', max: 100 },
|
||||||
{ name: '管理', max: 100 },
|
{ name: '管理', max: 100 },
|
||||||
{ name: '技术', max: 100 },
|
{ name: '技术', max: 100 },
|
||||||
{ name: '客服', max: 100 },
|
{ name: '客服', max: 100 },
|
||||||
{ name: '开发', max: 100 },
|
{ name: '开发', max: 100 },
|
||||||
]"
|
]"
|
||||||
:data="[
|
:data="[
|
||||||
{
|
{
|
||||||
name: '预算分配',
|
name: '预算分配',
|
||||||
value: [80, 70, 90, 85, 75],
|
value: [80, 70, 90, 85, 75],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '实际开销',
|
name: '实际开销',
|
||||||
value: [70, 75, 85, 80, 70],
|
value: [70, 75, 85, 80, 70],
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
||||||
<div class="fa-card">
|
<div class="fa-card">
|
||||||
<div class="pb-3.5">
|
<div class="pb-3.5">
|
||||||
<span class="text-base font-medium">k线图</span>
|
<span class="text-base font-medium">k线图</span>
|
||||||
</div>
|
</div>
|
||||||
<FaKLineChart
|
<FaKLineChart
|
||||||
:data="[
|
:data="[
|
||||||
{ time: '2024-01-01', open: 20, close: 23, high: 25, low: 18 },
|
{ time: '2024-01-01', open: 20, close: 23, high: 25, low: 18 },
|
||||||
{ time: '2024-01-02', open: 23, close: 21, high: 24, low: 20 },
|
{ time: '2024-01-02', open: 23, close: 21, high: 24, low: 20 },
|
||||||
{ time: '2024-01-03', open: 21, close: 25, high: 26, low: 21 },
|
{ time: '2024-01-03', open: 21, close: 25, high: 26, low: 21 },
|
||||||
]"
|
]"
|
||||||
:showDataZoom="false"
|
:showDataZoom="false"
|
||||||
:dataZoomStart="0"
|
:dataZoomStart="0"
|
||||||
:dataZoomEnd="100"
|
:dataZoomEnd="100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
<ElCol :xs="24" :md="12" :lg="8" class="mb-5">
|
||||||
<div class="fa-card">
|
<div class="fa-card">
|
||||||
<div class="pb-3.5">
|
<div class="pb-3.5">
|
||||||
<span class="text-base font-medium">双向堆叠柱状图</span>
|
<span class="text-base font-medium">双向堆叠柱状图</span>
|
||||||
</div>
|
</div>
|
||||||
<FaDualBarCompareChart
|
<FaDualBarCompareChart
|
||||||
:positiveData="[50, 28, 80, 65, 68, 70, 60, 55]"
|
:positiveData="[50, 28, 80, 65, 68, 70, 60, 55]"
|
||||||
:negativeData="[50, 28, 40, 45, 38, 50, 42, 48]"
|
:negativeData="[50, 28, 40, 45, 38, 50, 42, 48]"
|
||||||
:xAxisData="[
|
:xAxisData="[
|
||||||
'0-4岁',
|
'0-4岁',
|
||||||
'5-14岁',
|
'5-14岁',
|
||||||
'15-24岁',
|
'15-24岁',
|
||||||
'25-34岁',
|
'25-34岁',
|
||||||
'35-44岁',
|
'35-44岁',
|
||||||
'45-54岁',
|
'45-54岁',
|
||||||
'55-64岁',
|
'55-64岁',
|
||||||
'65岁以上',
|
'65岁以上',
|
||||||
]"
|
]"
|
||||||
positiveName="男性年龄分布"
|
positiveName="男性年龄分布"
|
||||||
negativeName="女性年龄分布"
|
negativeName="女性年龄分布"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
</div>
|
|
||||||
|
<div class="page-content" element-loading-text="加载中...">
|
||||||
|
<FaMapChart />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -325,9 +323,16 @@ import TargetVsReality from "./modules/target-vs-reality.vue";
|
|||||||
import TopProducts from "./modules/top-products.vue";
|
import TopProducts from "./modules/top-products.vue";
|
||||||
import SalesMappingByCountry from "./modules/sales-mapping-by-country.vue";
|
import SalesMappingByCountry from "./modules/sales-mapping-by-country.vue";
|
||||||
import VolumeServiceLevel from "./modules/volume-service-level.vue";
|
import VolumeServiceLevel from "./modules/volume-service-level.vue";
|
||||||
|
import { defineAsyncComponent } from "vue";
|
||||||
|
|
||||||
defineOptions({ name: "Analysis" });
|
defineOptions({ name: "Analysis" });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步加载地图组件
|
||||||
|
* 减少初始加载体积
|
||||||
|
*/
|
||||||
|
const FaMapChart = defineAsyncComponent(() => import("@/components/charts/fa-map-chart/index.vue"));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 多组数据折线图示例数据
|
* 多组数据折线图示例数据
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,511 +0,0 @@
|
|||||||
<!-- 工作台页面 -->
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<CardList></CardList>
|
|
||||||
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol :sm="24" :md="12" :lg="10">
|
|
||||||
<ActiveUser />
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :sm="24" :md="12" :lg="14">
|
|
||||||
<SalesOverview />
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol :sm="24" :md="24" :lg="12">
|
|
||||||
<NewUser />
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :sm="24" :md="12" :lg="6">
|
|
||||||
<Dynamic />
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :sm="24" :md="12" :lg="6">
|
|
||||||
<TodoList />
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
|
|
||||||
<AboutProject />
|
|
||||||
|
|
||||||
<!-- —— 卡片组件演示 —— -->
|
|
||||||
<div class="console-cards-showcase border-g-200">
|
|
||||||
<div class="py-5">
|
|
||||||
<h2 class="page-title">统计卡片(文字)</h2>
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol
|
|
||||||
v-for="card in statsCards"
|
|
||||||
:key="`t-${card.id}`"
|
|
||||||
:xs="24"
|
|
||||||
:sm="12"
|
|
||||||
:md="6"
|
|
||||||
class="mb-5"
|
|
||||||
>
|
|
||||||
<FaStatsCard
|
|
||||||
:icon="card.icon"
|
|
||||||
:iconStyle="card.iconStyle"
|
|
||||||
:title="card.title"
|
|
||||||
:description="card.description"
|
|
||||||
:showArrow="card.showArrow"
|
|
||||||
/>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
|
|
||||||
<h2 class="page-title">统计卡片(数字滚动)</h2>
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol
|
|
||||||
v-for="card in statsCards"
|
|
||||||
:key="`n-${card.id}`"
|
|
||||||
:xs="24"
|
|
||||||
:sm="12"
|
|
||||||
:md="6"
|
|
||||||
class="mb-5"
|
|
||||||
>
|
|
||||||
<FaStatsCard
|
|
||||||
:icon="card.icon"
|
|
||||||
:iconStyle="card.iconStyle"
|
|
||||||
:count="card.count"
|
|
||||||
:description="card.description"
|
|
||||||
:decimals="0"
|
|
||||||
:showArrow="card.showArrow"
|
|
||||||
separator=","
|
|
||||||
/>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
|
|
||||||
<h2 class="page-title">统计卡片(自定义样式)</h2>
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol
|
|
||||||
v-for="card in statsCards"
|
|
||||||
:key="`c-${card.id}`"
|
|
||||||
:xs="24"
|
|
||||||
:sm="12"
|
|
||||||
:md="6"
|
|
||||||
class="mb-5"
|
|
||||||
>
|
|
||||||
<FaStatsCard
|
|
||||||
:icon="card.icon"
|
|
||||||
:iconStyle="card.customIconStyle"
|
|
||||||
:boxStyle="card.boxStyle"
|
|
||||||
:title="card.title"
|
|
||||||
:description="card.description"
|
|
||||||
:textColor="card.textColor"
|
|
||||||
:showArrow="card.showArrow"
|
|
||||||
/>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
|
|
||||||
<h2 class="page-title">进度卡片</h2>
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol
|
|
||||||
v-for="card in progressCards"
|
|
||||||
:key="`p-${card.id}`"
|
|
||||||
:xs="24"
|
|
||||||
:sm="12"
|
|
||||||
:md="6"
|
|
||||||
class="mb-5"
|
|
||||||
>
|
|
||||||
<FaProgressCard :percentage="card.percentage" :title="card.title" :color="card.color" />
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
|
|
||||||
<h2 class="page-title">进度卡片(icon)</h2>
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol
|
|
||||||
v-for="card in progressCards"
|
|
||||||
:key="`pi-${card.id}`"
|
|
||||||
:xs="24"
|
|
||||||
:sm="12"
|
|
||||||
:md="6"
|
|
||||||
class="mb-5"
|
|
||||||
>
|
|
||||||
<FaProgressCard
|
|
||||||
:percentage="card.percentage"
|
|
||||||
:title="card.title"
|
|
||||||
:color="card.color"
|
|
||||||
:icon="card.icon"
|
|
||||||
:iconStyle="card.iconStyle"
|
|
||||||
/>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
|
|
||||||
<h2 class="page-title">图表卡片(小图表)</h2>
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
|
||||||
<FaLineChartCard
|
|
||||||
:isMiniChart="true"
|
|
||||||
:value="2545"
|
|
||||||
label="新用户"
|
|
||||||
date="过去7天"
|
|
||||||
:percentage="1.2"
|
|
||||||
:height="9.5"
|
|
||||||
:chartData="[120, 132, 101, 134, 90, 230, 210]"
|
|
||||||
/>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
|
||||||
<FaBarChartCard
|
|
||||||
:isMiniChart="true"
|
|
||||||
:value="15480"
|
|
||||||
label="浏览量"
|
|
||||||
date="过去 14 天"
|
|
||||||
:percentage="-4.15"
|
|
||||||
:height="9.5"
|
|
||||||
:barWidth="'45%'"
|
|
||||||
:chartData="[120, 100, 150, 140, 90, 120, 130]"
|
|
||||||
/>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
|
||||||
<FaLineChartCard
|
|
||||||
:isMiniChart="true"
|
|
||||||
:value="2545"
|
|
||||||
label="粉丝数"
|
|
||||||
date="过去 30 天"
|
|
||||||
:percentage="1.2"
|
|
||||||
:height="9.5"
|
|
||||||
:showAreaColor="true"
|
|
||||||
:chartData="[150, 180, 160, 200, 180, 220, 240]"
|
|
||||||
/>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
|
||||||
<FaDonutChartCard
|
|
||||||
:value="36358"
|
|
||||||
title="粉丝量"
|
|
||||||
:percentage="18"
|
|
||||||
percentageLabel="较去年"
|
|
||||||
:data="[50, 40]"
|
|
||||||
:height="9.5"
|
|
||||||
currentValue="2022"
|
|
||||||
previousValue="2021"
|
|
||||||
:radius="['50%', '70%']"
|
|
||||||
/>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
|
|
||||||
<h2 class="page-title">图表卡片(大图表)</h2>
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
|
||||||
<FaLineChartCard
|
|
||||||
:value="2545"
|
|
||||||
label="新用户"
|
|
||||||
:percentage="1.2"
|
|
||||||
:height="11"
|
|
||||||
:chartData="[120, 132, 101, 134, 90, 230, 210]"
|
|
||||||
/>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
|
||||||
<FaBarChartCard
|
|
||||||
:value="15480"
|
|
||||||
label="浏览量"
|
|
||||||
:percentage="-4.15"
|
|
||||||
:height="11"
|
|
||||||
:chartData="[120, 100, 150, 140, 90, 120, 130, 110]"
|
|
||||||
/>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
|
||||||
<FaLineChartCard
|
|
||||||
:value="2545"
|
|
||||||
label="粉丝数"
|
|
||||||
:percentage="1.2"
|
|
||||||
:height="11"
|
|
||||||
:showAreaColor="true"
|
|
||||||
:chartData="[150, 180, 160, 200, 180, 220, 240]"
|
|
||||||
/>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :xs="24" :sm="12" :md="6" class="mb-5">
|
|
||||||
<FaDonutChartCard
|
|
||||||
:value="36358"
|
|
||||||
title="粉丝量"
|
|
||||||
:percentage="-18"
|
|
||||||
percentageLabel="较2021年"
|
|
||||||
:data="[70, 30]"
|
|
||||||
:height="11"
|
|
||||||
currentValue="12月"
|
|
||||||
previousValue="11月"
|
|
||||||
/>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
|
|
||||||
<h2 class="page-title">数据列表卡片</h2>
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol :xs="24" :sm="12" :lg="8" class="mb-5">
|
|
||||||
<FaDataListCard :list="dataList" title="待办事项" subtitle="今日待处理任务" />
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :xs="24" :sm="12" :lg="8" class="mb-5">
|
|
||||||
<FaDataListCard
|
|
||||||
:maxCount="4"
|
|
||||||
:list="dataList"
|
|
||||||
title="最近活动"
|
|
||||||
subtitle="近期活动列表"
|
|
||||||
:showMoreButton="true"
|
|
||||||
@more="handleMore"
|
|
||||||
/>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :xs="24" :sm="12" :lg="8" class="mb-5">
|
|
||||||
<FaTimelineListCard :list="timelineData" title="最近交易" subtitle="2024年12月20日" />
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
|
|
||||||
<h2 class="page-title">图片卡片</h2>
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol v-for="card in imageCards" :key="card.id" :xs="24" :sm="12" :md="6" class="mb-5">
|
|
||||||
<FaImageCard
|
|
||||||
:imageUrl="card.imageUrl"
|
|
||||||
:title="card.title"
|
|
||||||
:category="card.category"
|
|
||||||
:readTime="card.readTime"
|
|
||||||
:views="card.views"
|
|
||||||
:comments="card.comments"
|
|
||||||
:date="card.date"
|
|
||||||
@click="handleImageCardClick(card)"
|
|
||||||
/>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ElMessage } from "element-plus";
|
|
||||||
import cover1 from "@imgs/cover/img1.webp";
|
|
||||||
import cover2 from "@imgs/cover/img2.webp";
|
|
||||||
import cover3 from "@imgs/cover/img3.webp";
|
|
||||||
import cover4 from "@imgs/cover/img4.webp";
|
|
||||||
import CardList from "./modules/card-list.vue";
|
|
||||||
import ActiveUser from "./modules/active-user.vue";
|
|
||||||
import SalesOverview from "./modules/sales-overview.vue";
|
|
||||||
import NewUser from "./modules/new-user.vue";
|
|
||||||
import Dynamic from "./modules/dynamic-stats.vue";
|
|
||||||
import TodoList from "./modules/todo-list.vue";
|
|
||||||
import AboutProject from "./modules/about-project.vue";
|
|
||||||
|
|
||||||
defineOptions({ name: "Console" });
|
|
||||||
|
|
||||||
const statsCards = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: "销售产品",
|
|
||||||
count: 1235,
|
|
||||||
description: "鞋子、牛仔裤、派对服装、手表",
|
|
||||||
icon: "ri:bar-chart-box-line",
|
|
||||||
boxStyle: "!bg-theme/10",
|
|
||||||
customIconStyle: "!text-theme !text-3xl",
|
|
||||||
iconStyle: "bg-info",
|
|
||||||
textColor: "var(--theme-color)",
|
|
||||||
showArrow: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: "活跃用户",
|
|
||||||
count: 5000,
|
|
||||||
description: "日活跃用户超过5,000+",
|
|
||||||
icon: "ri:account-box-2-line",
|
|
||||||
boxStyle: "!bg-warning/10",
|
|
||||||
customIconStyle: "!text-warning !text-3xl",
|
|
||||||
iconStyle: "bg-warning",
|
|
||||||
textColor: "var(--fa-warning)",
|
|
||||||
showArrow: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: "总收入",
|
|
||||||
count: 35000,
|
|
||||||
description: "月收入超过¥350,000+",
|
|
||||||
icon: "ri:money-cny-box-line",
|
|
||||||
boxStyle: "!bg-secondary/10",
|
|
||||||
customIconStyle: "!text-secondary !text-3xl",
|
|
||||||
iconStyle: "bg-secondary",
|
|
||||||
textColor: "var(--fa-secondary)",
|
|
||||||
showArrow: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: "客户评价",
|
|
||||||
count: 4800,
|
|
||||||
description: "平均评分4.8/5",
|
|
||||||
icon: "ri:message-3-line",
|
|
||||||
boxStyle: "!bg-error/10",
|
|
||||||
customIconStyle: "!text-error !text-3xl",
|
|
||||||
iconStyle: "bg-error",
|
|
||||||
textColor: "var(--fa-error)",
|
|
||||||
showArrow: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const progressCards = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: "完成进度",
|
|
||||||
percentage: 75,
|
|
||||||
color: "var(--fa-success)",
|
|
||||||
icon: "ri:arrow-up-circle-line",
|
|
||||||
iconStyle: "bg-success/12 text-success",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: "项目进度",
|
|
||||||
percentage: 65,
|
|
||||||
color: "var(--theme-color)",
|
|
||||||
icon: "ri:twitch-line",
|
|
||||||
iconStyle: "bg-theme/12 text-theme",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: "学习进度",
|
|
||||||
percentage: 45,
|
|
||||||
color: "var(--fa-error)",
|
|
||||||
icon: "ri:game-line",
|
|
||||||
iconStyle: "!bg-error/12 text-error",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: "任务进度",
|
|
||||||
percentage: 90,
|
|
||||||
color: "var(--fa-secondary)",
|
|
||||||
icon: "ri:flag-2-line",
|
|
||||||
iconStyle: "bg-secondary/12 text-secondary",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const imageCards = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
imageUrl: cover1,
|
|
||||||
title: "AI技术在医疗领域的创新应用与发展前景",
|
|
||||||
category: "社交",
|
|
||||||
readTime: "2分钟",
|
|
||||||
views: 9125,
|
|
||||||
comments: 3,
|
|
||||||
date: "12月19日 周一",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
imageUrl: cover2,
|
|
||||||
title: "大数据分析助力企业决策的实践案例",
|
|
||||||
category: "技术",
|
|
||||||
readTime: "3分钟",
|
|
||||||
views: 7234,
|
|
||||||
comments: 5,
|
|
||||||
date: "12月20日 周二",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
imageUrl: cover3,
|
|
||||||
title: "区块链技术在供应链管理中的应用",
|
|
||||||
category: "科技",
|
|
||||||
readTime: "4分钟",
|
|
||||||
views: 5678,
|
|
||||||
comments: 8,
|
|
||||||
date: "12月21日 周三",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
imageUrl: cover4,
|
|
||||||
title: "云计算技术发展趋势与未来展望",
|
|
||||||
category: "云技术",
|
|
||||||
readTime: "5分钟",
|
|
||||||
views: 4321,
|
|
||||||
comments: 6,
|
|
||||||
date: "12月22日 周四",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const dataList = [
|
|
||||||
{
|
|
||||||
title: "新加坡之行",
|
|
||||||
status: "进行中",
|
|
||||||
time: "5分钟",
|
|
||||||
class: "bg-theme/12 text-theme",
|
|
||||||
icon: "ri:camera-4-line",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "归档数据",
|
|
||||||
status: "进行中",
|
|
||||||
time: "10分钟",
|
|
||||||
class: "bg-secondary/12 text-secondary",
|
|
||||||
icon: "ri:bar-chart-box-line",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "客户会议",
|
|
||||||
status: "待处理",
|
|
||||||
time: "15分钟",
|
|
||||||
class: "bg-warning/12 text-warning",
|
|
||||||
icon: "ri:user-3-line",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "筛选任务团队",
|
|
||||||
status: "进行中",
|
|
||||||
time: "20分钟",
|
|
||||||
class: "bg-error/12 text-error",
|
|
||||||
icon: "ri:account-circle-line",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "发送信封给小王",
|
|
||||||
status: "已完成",
|
|
||||||
time: "20分钟",
|
|
||||||
class: "bg-success/12 text-success",
|
|
||||||
icon: "ri:message-3-line",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const timelineData = [
|
|
||||||
{
|
|
||||||
time: "上午 09:30",
|
|
||||||
status: "rgb(73, 190, 255)",
|
|
||||||
content: "收到 John Doe 支付的 385.90 美元",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: "上午 10:00",
|
|
||||||
status: "rgb(54, 158, 255)",
|
|
||||||
content: "新销售记录",
|
|
||||||
code: "ML-3467",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: "上午 12:00",
|
|
||||||
status: "rgb(103, 232, 207)",
|
|
||||||
content: "向 Michael 支付了 64.95 美元",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: "下午 14:30",
|
|
||||||
status: "rgb(255, 193, 7)",
|
|
||||||
content: "系统维护通知",
|
|
||||||
code: "MT-2023",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: "下午 15:45",
|
|
||||||
status: "rgb(255, 105, 105)",
|
|
||||||
content: "紧急订单取消提醒",
|
|
||||||
code: "OR-9876",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: "下午 17:00",
|
|
||||||
status: "rgb(103, 232, 207)",
|
|
||||||
content: "完成每日销售报表",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function handleMore() {
|
|
||||||
ElMessage.info("查看更多");
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ImageCard {
|
|
||||||
id: number;
|
|
||||||
imageUrl: string;
|
|
||||||
title: string;
|
|
||||||
category: string;
|
|
||||||
readTime: string;
|
|
||||||
views: number;
|
|
||||||
comments: number;
|
|
||||||
date: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleImageCardClick(card: ImageCard) {
|
|
||||||
console.log("点击卡片:", card);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
@reference "@styles/core/tailwind.css";
|
|
||||||
|
|
||||||
.page-title {
|
|
||||||
@apply my-5 text-xl font-medium first:mt-0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
<!-- 电子商务页面 -->
|
|
||||||
<template>
|
|
||||||
<div class="ecommerce">
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol :sm="24" :md="24" :lg="16">
|
|
||||||
<Banner />
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :sm="12" :md="12" :lg="4">
|
|
||||||
<TotalOrderVolume />
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :sm="12" :md="12" :lg="4">
|
|
||||||
<TotalProducts />
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol :sm="12" :md="12" :lg="8">
|
|
||||||
<SalesTrend />
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :sm="12" :md="12" :lg="8">
|
|
||||||
<SalesClassification />
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :sm="24" :md="24" :lg="8">
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol :sm="24" :md="12" :lg="12">
|
|
||||||
<ProductSales />
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :sm="24" :md="12" :lg="12">
|
|
||||||
<SalesGrowth />
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :span="24" class="no-margin-bottom">
|
|
||||||
<CartConversionRate />
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol :sm="24" :md="12" :lg="8">
|
|
||||||
<HotCommodity />
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :sm="24" :md="12" :lg="8">
|
|
||||||
<AnnualSales />
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :sm="24" :md="24" :lg="8">
|
|
||||||
<TransactionList />
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol :md="24" :lg="8">
|
|
||||||
<RecentTransaction />
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :md="24" :lg="16" class="no-margin-bottom">
|
|
||||||
<HotProductsList />
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import Banner from "./modules/banner.vue";
|
|
||||||
import TotalOrderVolume from "./modules/total-order-volume.vue";
|
|
||||||
import TotalProducts from "./modules/total-products.vue";
|
|
||||||
import SalesTrend from "./modules/sales-trend.vue";
|
|
||||||
import SalesClassification from "./modules/sales-classification.vue";
|
|
||||||
import TransactionList from "./modules/transaction-list.vue";
|
|
||||||
import HotCommodity from "./modules/hot-commodity.vue";
|
|
||||||
import RecentTransaction from "./modules/recent-transaction.vue";
|
|
||||||
import AnnualSales from "./modules/annual-sales.vue";
|
|
||||||
import ProductSales from "./modules/product-sales.vue";
|
|
||||||
import SalesGrowth from "./modules/sales-growth.vue";
|
|
||||||
import CartConversionRate from "./modules/cart-conversion-rate.vue";
|
|
||||||
import HotProductsList from "./modules/hot-products-list.vue";
|
|
||||||
|
|
||||||
defineOptions({ name: "Ecommerce" });
|
|
||||||
</script>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="page-content mb-5" element-loading-text="加载中...">
|
|
||||||
<FaMapChart />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { defineAsyncComponent } from "vue";
|
|
||||||
|
|
||||||
defineOptions({ name: "DashboardMap" });
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 异步加载地图组件
|
|
||||||
* 减少初始加载体积
|
|
||||||
*/
|
|
||||||
const FaMapChart = defineAsyncComponent(() => import("@/components/charts/fa-map-chart/index.vue"));
|
|
||||||
</script>
|
|
||||||