refactor: 重构路由与部署脚本,更新静态资源路径

This commit is contained in:
zhangtao
2026-05-13 01:55:47 +08:00
parent db5f8430de
commit 63d76007cb
69 changed files with 1688 additions and 2794 deletions
+7 -7
View File
@@ -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](backend/data/login.png) | | Login | ![login](frontend/docs/src/public/login.png) |
| Dashboard | ![Dashboard](backend/data/dashboard.png) | | Dashboard | ![Dashboard](frontend/docs/src/public/dashboard.png) |
| Generator | ![Generator](backend/data/gencode.png) | | Generator | ![Generator](frontend/docs/src/public/gencode.png) |
| AI | ![AI](backend/data/ai.png) | | AI | ![AI](frontend/docs/src/public/ai.png) |
### 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"/> |
|----------|----------|----------| |----------|----------|----------|
| ![Mobile Login](backend/data/app_login.png) | ![Mobile Home](backend/data/app_home.png) | ![Mobile Personal Info](backend/data/app_mine.png) | | ![Mobile Login](frontend/docs/src/public/app_login.png) | ![Mobile Home](frontend/docs/src/public/app_home.png) | ![Mobile Personal Info](frontend/docs/src/public/app_mine.png) |
## 🚀 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 |
| --- | --- | | --- | --- |
| ![Group QR Code](backend/data/group.jpg) | ![WeChat Pay QR Code](backend/data/wechatPay.jpg) | | ![Group QR Code](frontend/docs/src/public/group.jpg) | ![WeChat Pay QR Code](frontend/docs/src/public/wechatPay.jpg) |
## ❤️ Support the Project ## ❤️ Support the Project
+7 -7
View File
@@ -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"/> | 截图 |
| ----- | --- | | ----- | --- |
| 登录 | ![仪表盘](backend/data/login.png) | | 登录 | ![仪表盘](frontend/docs/src/public/login.png) |
| 仪表盘 | ![仪表盘](backend/data/dashboard.png) | | 仪表盘 | ![仪表盘](frontend/docs/src/public/dashboard.png) |
| 代码生成 | ![代码生成](backend/data/gencode.png) | | 代码生成 | ![代码生成](frontend/docs/src/public/gencode.png) |
| 智能助手 | ![智能助手](backend/data/ai.png) | | 智能助手 | ![智能助手](frontend/docs/src/public/ai.png) |
### 移动端 ### 移动端
| 登录 <div style="width:60px"/> | 首页 <div style="width:60px"/> | 个人中心 <div style="width:60px"/> | | 登录 <div style="width:60px"/> | 首页 <div style="width:60px"/> | 个人中心 <div style="width:60px"/> |
|----------|----------|----------| |----------|----------|----------|
| ![移动端登录](backend/data/app_login.png) | ![移动端首页](backend/data/app_home.png) | ![移动端个人中心](backend/data/app_mine.png) | | ![移动端登录](frontend/docs/src/public/app_login.png) | ![移动端首页](frontend/docs/src/public/app_home.png) | ![移动端个人中心](frontend/docs/src/public/app_mine.png) |
## 🚀 快速开始 ## 🚀 快速开始
@@ -613,7 +613,7 @@ A:使用 `./deploy.sh` 脚本一键部署到生产环境。
| 群组二维码 | 微信支付二维码 | | 群组二维码 | 微信支付二维码 |
| --- | --- | | --- | --- |
| ![群组二维码](backend/data/group.jpg) | ![微信支付二维码](backend/data/wechatPay.jpg) | | ![群组二维码](frontend/docs/src/public/group.jpg) | ![微信支付二维码](frontend/docs/src/public/wechatPay.jpg) |
## ❤️ 支持项目 ## ❤️ 支持项目
-7
View File
@@ -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 "未知"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 604 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1013 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

-446
View File
@@ -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"/> | 截图 |
| ----- | --- |
| 仪表盘 | ![仪表盘](https://gitee.com/fastapiadmin/FastDocs/raw/master/docs/public/dashboard.png) |
| 代码生成 | ![代码生成](https://gitee.com/fastapiadmin/FastDocs/raw/master/docs/public/gencode.png) |
| 智能助手 | ![智能助手](https://gitee.com/fastapiadmin/FastDocs/raw/master/docs/public/ai.png) |
### 移动端
| 登录 <div style="width:60px"/> | 首页 <div style="width:60px"/> | 个人中心 <div style="width:60px"/> |
|----------|----------|----------|
| ![移动端登录](https://gitee.com/fastapiadmin/FastDocs/raw/master/docs/public/app_login.png) | ![移动端首页](https://gitee.com/fastapiadmin/FastDocs/raw/master/docs/public/app_home.png) | ![移动端个人中心](https://gitee.com/fastapiadmin/FastDocs/raw/master/docs/public/app_mine.png) |
## 🚀 快速开始
### 环境要求
| 类型 | 技术栈 | 版本 |
|------|--------|------|
| 后端 | 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)
+52 -42
View File
@@ -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

-73
View File
@@ -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

-71
View File
@@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 KiB

+103 -297
View File
@@ -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=" set "RED="
set "GREEN=" set "GREEN="
set "YELLOW=" set "YELLOW="
@@ -29,12 +12,8 @@ set "BLUE="
set "CYAN=" set "CYAN="
set "RESET=" set "RESET="
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=" echo %~nx0 stop - 停止所有服务
echo %GREEN% %~nx0 stop %RESET% - 停止所有服务 echo %~nx0 restart - 重启所有服务
set "YELLOW=" echo %~nx0 status - 检查服务状态
echo %GREEN% %~nx0 restart %RESET% - 重启所有服务
set "YELLOW="
echo %GREEN% %~nx0 status %RESET% - 检查服务状态
set "YELLOW="
echo %GREEN% %~nx0 help %RESET% - 显示此帮助信息
set "YELLOW="
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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 384 KiB

After

Width:  |  Height:  |  Size: 721 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 608 KiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 697 KiB

After

Width:  |  Height:  |  Size: 1013 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 248 KiB

Before

Width:  |  Height:  |  Size: 752 KiB

After

Width:  |  Height:  |  Size: 752 KiB

+4 -4
View File
@@ -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
+2 -2
View File
@@ -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% 能正确解析
> * { > * {
+8 -8
View File
@@ -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" },
}, },
], ],
+1 -3
View File
@@ -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"
}, },
+1 -3
View File
@@ -545,10 +545,8 @@
}, },
"dashboard": { "dashboard": {
"title": "仪表盘", "title": "仪表盘",
"console": "控制台",
"analysis": "分析页", "analysis": "分析页",
"ecommerce": "电子商务", "visualization": "三维可视化",
"map": "地图",
"pricing": "定价", "pricing": "定价",
"tutorial": "操作手册" "tutorial": "操作手册"
}, },
+62 -99
View File
@@ -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"),
},
],
},
], ],
}, },
{ {
+1 -1
View File
@@ -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>
+490 -597
View File
File diff suppressed because it is too large Load Diff