Merge branch 'dev'

# Conflicts:
#	README.md
#	generator-code-server/generator-code/pom.xml
#	generator-code-server/generator-code/src/main/java/cn/bunny/core/factory/AbstractDatabaseInfo.java
#	generator-code-server/generator-code/src/main/java/cn/bunny/core/factory/ConcreteDatabaseInfo.java
#	generator-code-server/generator-code/src/main/java/cn/bunny/service/VmsService.java
#	generator-code-server/generator-code/src/main/java/cn/bunny/service/impl/SqlParserServiceImpl.java
#	generator-code-server/generator-code/src/main/java/cn/bunny/service/impl/TableServiceImpl.java
#	generator-code-server/generator-code/src/main/java/cn/bunny/service/impl/VmsServiceImpl.java
#	generator-code-server/generator-code/src/main/java/cn/bunny/utils/VmsUtil.java
#	src/main/java/cn/bunny/controller/IndexController.java
#	src/main/java/cn/bunny/controller/TableController.java
#	src/main/java/cn/bunny/controller/VmsController.java
#	src/main/java/cn/bunny/core/provider/SqlMetadataProvider.java
#	src/main/java/cn/bunny/core/template/VmsTBaseTemplateGenerator.java
#	src/main/java/cn/bunny/domain/dto/VmsArgumentDto.java
#	src/main/resources/application-prod.yml
#	src/main/resources/logback.xml
#	src/main/resources/static/src/components/AppGeneratorPage.js
#	src/main/resources/static/src/config/axios-config.js
#	src/main/resources/static/src/config/highlight-config.js
#	src/main/resources/static/src/config/popper-config.js
#	src/main/resources/static/src/lib/css/bootstrap/bootstrap.min.css
#	src/main/resources/static/src/lib/css/fonts/bootstrap-icons.woff
#	src/main/resources/static/src/lib/css/fonts/bootstrap-icons.woff2
#	src/main/resources/static/src/lib/css/highlight/atom-one-dark.min.css
#	src/main/resources/static/src/lib/js/axios/axios.min.js
#	src/main/resources/static/src/lib/js/boostrap/bootstrap.bundle.min.js
#	src/main/resources/static/src/lib/js/boostrap/popper.min.js
#	src/main/resources/static/src/lib/js/dayjs/advancedFormat.js
#	src/main/resources/static/src/lib/js/dayjs/antd.min.js
#	src/main/resources/static/src/lib/js/dayjs/customParseFormat.js
#	src/main/resources/static/src/lib/js/dayjs/dayjs.min.js
#	src/main/resources/static/src/lib/js/dayjs/localeData.js
#	src/main/resources/static/src/lib/js/dayjs/quarterOfYear.js
#	src/main/resources/static/src/lib/js/dayjs/weekOfYear.js
#	src/main/resources/static/src/lib/js/dayjs/weekYear.js
#	src/main/resources/static/src/lib/js/dayjs/weekday.js
#	src/main/resources/static/src/lib/js/highlightjs/highlight.min.js
#	src/main/resources/static/src/lib/js/highlightjs/javascript.min.js
#	src/main/resources/static/src/lib/js/vue/vue.global.js
#	src/main/resources/static/src/lib/js/vue/vue.global.prod.js
#	src/main/resources/static/src/views/database/DatabaseCard.js
#	src/main/resources/static/src/views/database/DatabaseForm.js
#	src/main/resources/templates/database.html
This commit is contained in:
bunny 2025-07-02 21:28:51 +08:00
commit d9cced0eba
220 changed files with 23527 additions and 10506 deletions

2
.gitignore vendored
View File

@ -12,7 +12,7 @@ dist-ssr
.eslintcache
report.html
vite.config.*.timestamp*
application-prod.yml
bunny-web.site.csr
bunny-web.site.key
bunny-web.site_bundle.crt

443
README.md
View File

@ -1,53 +1,402 @@
# 代码生成器
# 🚀 Bunny Code Generator 代码生成器系统文档
## 功能展示
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)[![Java Version](https://img.shields.io/badge/JDK-17-green.svg)]()[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.4.3-6DB33F.svg)]()
点击 `表名``注释内容` 跳转到另一个页面
## 1. 系统架构 🏗️
![image-20250419132154669](./images/image-20250419132154669.png)
### 1.1 架构图
![image-20250422202525702](./images/image-20250422202525702.png)
![image-20250422202618670](./images/image-20250422202618670.png)
## 内置字段
```java
// vm 不能直接写 `{` 需要转换下
context.put("leftBrace", "{");
// 当前的表名
context.put("tableName", tableMetaData.getTableName());
// 当前表的列信息
context.put("columnInfoList", columnInfoList);
// 数据库sql列
context.put("baseColumnList", String.join(",", list));
// 当前日期
String date = new SimpleDateFormat(dto.getSimpleDateFormat()).format(new Date());
context.put("date", date);
// 作者名字
context.put("author", dto.getAuthor());
// 每个 Controller 上的请求前缀
context.put("requestMapping", dto.getRequestMapping());
// 表字段的注释内容
context.put("comment", dto.getComment());
// 设置包名称
context.put("package", dto.getPackageName());
// 将类名称转成小驼峰
String toCamelCase = TypeConvertCore.convertToCamelCase(replaceTableName);
context.put("classLowercaseName", toCamelCase);
// 将类名称转成大驼峰
String convertToCamelCase = TypeConvertCore.convertToCamelCase(replaceTableName, true);
context.put("classUppercaseName", convertToCamelCase);
```mermaid
graph TD
A[🖥️ 前端界面] -->|HTTP请求| B[🌐 WebController]
A -->|API调用| C[⚙️ GeneratorController]
A -->|SQL解析| D[🔍 SqlParserController]
A -->|元数据查询| E[🗃️ TableController]
A -->|模板管理| F[📂 VmsController]
B -->|页面跳转| G[📝 Thymeleaf模板]
C -->|生成请求| H[🏭 GeneratorService]
D -->|SQL解析| I[✂️ SqlMetadataProvider]
E -->|数据库查询| J[🔗 DatabaseMetadataProvider]
F -->|模板扫描| K[🗃️ VmsService]
H -->|模板渲染| L[🌀 Velocity引擎]
H -->|打包下载| M[📦 ZipFileUtil]
I & J -->|数据源| N[💾 MySQL数据库]
J -->|连接池| O[🛟 HikariCP]
```
![wx+alipay](images/wx_alipay.png)
### 1.2 核心分层 🔍
| 层级 | 组件 | 技术实现 |
| ------------ | --------------- | -------------------- |
| **接入层** 🚪 | Controllers | Spring Web, Swagger |
| **业务层** ⚙️ | Services | 并行流, 设计模式 |
| **核心层** 🧠 | 模板生成/元数据 | Velocity, JSqlParser |
| **数据层** 💾 | 数据源/连接池 | HikariCP, JDBC |
---
## 2. 核心控制器详解 🎯
### 2.1 GeneratorController ⚡
**核心业务流:**
```mermaid
flowchart TB
A[接收VmsArgumentDto] --> B{判断生成方式}
B -->|SQL生成| C[调用generateCodeBySql]
B -->|数据库生成| D[调用generateCodeByDatabase]
C/D --> E[模板渲染]
E --> F{是否打包下载}
F -->|是| G[生成ZIP响应]
F -->|否| H[返回代码预览]
```
**关键方法说明:**
1. `generator()` 方法:
- 🔄 根据`sql`参数判断生成方式
- 📊 使用`VmsArgumentDto`接收模板选择、包名等参数
- 🌟 核心逻辑:
```java
Strings.isEmpty(sql)
? generatorService.generateCodeByDatabase(dto)
: generatorService.generateCodeBySql(dto);
```
2. `downloadByZip()` 方法:
- 🗜️ 使用`ZipFileUtil`进行内存压缩
- ⚡ 响应头设置:
```java
headers.add("Content-Disposition", "attachment; filename=" + zipFilename);
```
### 2.2 TableController 🗃️
**元数据查询体系:**
```mermaid
classDiagram
TableController --> TableService : 依赖
TableService --> DatabaseMetadataProvider : 调用
DatabaseMetadataProvider --> HikariDataSource : 使用
class TableController{
+databaseInfoMetaData() 数据库基础信息
+databaseTableList() 表清单
+tableMetaData() 单表结构
+tableColumnInfo() 列详情
}
```
**典型调用链:**
1. 获取数据库信息:
```java
DatabaseMetaData metaData = connection.getMetaData();
return DatabaseInfoMetaData.builder()
.databaseProductName(metaData.getDatabaseProductName())
.driverVersion(metaData.getDriverVersion())
.build();
```
2. 查询表结构:
```java
ResultSet tables = metaData.getTables(dbName, null, "%", new String[]{"TABLE"});
while(tables.next()){
// 构建TableMetaData对象
}
```
## 3. 关键技术实现 🔧
### 3.1 模板生成流程 🌀
**抽象模板方法模式:**
```java
public abstract class AbstractTemplateGenerator {
// 模板方法
public final StringWriter generateCode() {
prepareContext(); // 准备数据
return mergeTemplate(); // 渲染模板
}
protected abstract void addContext(); // 子类实现
}
```
**Velocity上下文示例**
```java
context.put("classLowercaseName", "userInfo");
context.put("columns", columnList);
context.put("date", "2023-01-01");
```
### 3.2 类型转换策略 🔄
**MySQL类型映射**
| 数据库类型 | Java类型 | 转换方法 |
| ---------- | --------------- | ------------------------------------------ |
| `varchar` | `String` | `MysqlTypeConvertUtil.convertToJavaType()` |
| `datetime` | `LocalDateTime` | 同上 |
| `tinyint` | `Integer` | 同上 |
**命名转换示例:**
```java
// 下划线转驼峰
convertToCamelCase("user_name", true); // → "UserName"
convertToCamelCase("user_name", false); // → "userName"
```
## 4. 核心业务逻辑说明
### 4.1 代码生成流程
#### 4.1.1 简洁流畅
```mermaid
sequenceDiagram
participant F as 前端
participant C as Controller
participant S as Service
participant T as Template
F->>C: 提交生成请求(VmsArgumentDto)
C->>S: 调用generateCodeBy[Sql/Database]
S->>T: 准备Velocity上下文
T->>S: 返回渲染结果
S->>C: 返回Map<String, List<GeneratorVo>>
C->>F: 返回结果或ZIP包
```
### 4.1.2 详细流程
```mermaid
sequenceDiagram
participant 前端
participant GeneratorController
participant GeneratorService
participant MetadataProvider
participant TemplateEngine
前端->>GeneratorController: 提交生成请求
GeneratorController->>GeneratorService: 调用生成方法
GeneratorService->>MetadataProvider: 获取表/列元数据
MetadataProvider-->>GeneratorService: 返回元数据
GeneratorService->>TemplateEngine: 填充模板
TemplateEngine-->>GeneratorService: 生成代码
GeneratorService-->>GeneratorController: 返回生成结果
GeneratorController-->>前端: 返回代码/ZIP包
```
### 4.2 关键类协作
```mermaid
classDiagram
class GeneratorController {
+generateCode()
+downloadByZip()
}
class GeneratorService {
+generateCodeByDatabase()
+generateCodeBySql()
}
class AbstractTemplateGenerator {
+generateCode()
#addContext()
#templateMerge()
}
class VmsTBaseTemplateGenerator {
+addContext()
+templateMerge()
}
GeneratorController --> GeneratorService
GeneratorService --> AbstractTemplateGenerator
AbstractTemplateGenerator <|-- VmsTBaseTemplateGenerator
```
## 5. 扩展指南 🛠️
### 5.1 添加新数据库支持
1. 实现`DatabaseDialect`接口 ✏️
2. 配置新的`IMetadataProvider` ⚙️
3. 示例代码结构:
```java
@Component
public class OracleDialect implements DatabaseDialect {
// 实现提取注释等方法
}
```
### 5.2 依赖管理 📦
**关键依赖说明:**
```xml
<!-- SQL解析 -->
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>4.9</version>
</dependency>
<!-- 模板引擎 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
```
## 6. 注意事项 ⚠️
### 6.1 性能优化点 🚀
- `parallelStream()` 用于批量生成
- `HikariCP` 配置:
```yaml
hikari:
maximum-pool-size: 20
connection-timeout: 30000
```
### 6.2 版本兼容性 🔗
| 组件 | 已验证版本 |
| ----------- | ---------- |
| MySQL | 8.0+ |
| JDK | 17+ |
| Spring Boot | 3.4.3 |
### 6.3 文件名重复问题解决方案⚠️
```mermaid
graph TD
A[用户选择模板] --> B{是否包含$className}
B -->|是| C[替换为表名的小驼峰格式]
B -->|否| D[保持原文件名]
C --> E[生成唯一文件路径]
D --> E
```
**核心逻辑实现**(位于`VmsGeneratorPathHelper.java`
```java
public static String processVmPath(VmsArgumentDto dto, String path, String tableName) {
String className = removeTablePrefixes(dto, tableName);
String lowerCamelCase = MysqlTypeConvertUtil.convertToCamelCase(tableName, false);
// 关键替换逻辑👇
String[] pathParts = path.replace("$className", lowerCamelCase).split("/");
...
}
```
**必须遵守的规则**
> [!Note]
>
> 如果不想使用`$className`可自己修改源码,进行更改。
1. 前端文件必须使用`$className`作为动态目录名(如:`web/$className/api.ts`
2. 相同基础名称的文件必须放在不同目录下
3. Java/XML文件会自动添加类型后缀如`UserController.java`
### 6.4 文件命名冲突场景示例
| 错误案例 | 正确方案 | 原因 |
| ----------------------- | -------------------------- | -------------------------- |
| `web/index.vue` | `web/$className/index.vue` | 多表生成时会冲突 |
| `mapper/UserMapper.xml` | 自动处理 | 系统会自动添加表名前缀 |
| `service/Service.java` | 自动处理 | 会转换为`UserService.java` |
## 7. 代码质量评估 🔍
### 7.1 后端代码评估85/100
**优势**
- ✅ 清晰的层次划分Controller/Service/Provider
- ✅ 合理使用设计模式(模板方法模式)
- ✅ 完善的异常处理体系
- ✅ 类型转换工具类封装良好
**待改进**
- ⚠️ 部分SQL解析逻辑可抽取为策略模式
- ⚠️ 元数据提供者接口可进一步抽象
- ⚠️ ZIP打包逻辑与业务耦合稍紧
**坏味道检测**
```mermaid
pie
title 后端代码坏味道分布
"重复代码" : 15
"过长方法" : 10
"过度耦合" : 5
"其他" : 70
```
### 7.2 前端代码评估78/100
**亮点**
- ✅ 组件化设计合理(表单/表格分离)
- ✅ 响应式状态管理有效
- ✅ 良好的用户交互反馈
- ✅ 类型提示完善
**问题点**
- ⚠️ 部分表单验证逻辑重复
- ⚠️ 表格分页逻辑可抽取为独立组件
**复杂度分析**
| 文件 | 方法数 | 平均行数 | 复杂度 |
| ------------------- | ------ | -------- | ------ |
| DatabaseTable.js | 12 | 18 | 中等 |
| DatabaseForm.js | 25 | 12 | 较高 |
| AppGeneratorPage.js | 8 | 15 | 低 |
### 7.3 总结评价
**整体评分**82/100
**可维护性**:⭐️⭐️⭐️⭐️
**扩展性**:⭐️⭐️⭐️⭐️
**代码规范**:⭐️⭐️⭐️⭐️⭐️
## 8. 支持项目☕
如果这个项目对您有帮助,可以考虑支持我们:
![WeChat & Alipay](D:\Project\FullStack\GeneratorCode\images\wx_alipay.png)
**Happy Coding!** 🎉

View File

@ -1,113 +0,0 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>generator-code-server</artifactId>
<version>3.4.3</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>generator-code</artifactId>
<packaging>jar</packaging>
<name>generator-code</name>
<url>https://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>5.1</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- knife4j -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.14</version>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!-- 小驼峰 和 大驼峰之间互转 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.4.7-jre</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
</dependencies>
</project>

View File

@ -1,13 +0,0 @@
package cn.bunny.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
@GetMapping("/")
public String index() {
return "index";
}
}

View File

@ -1,39 +0,0 @@
package cn.bunny.controller;
import cn.bunny.core.factory.ConcreteSqlParserDatabaseInfo;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.domain.result.Result;
import cn.bunny.service.SqlParserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@Tag(name = "解析SQL", description = "解析SQL接口")
@RestController
@RequestMapping("/api/sqlParser")
public class SqlParserController {
@Resource
private SqlParserService sqlParserService;
@Operation(summary = "解析SQL成表信息", description = "解析SQL成表信息")
@PostMapping("tableInfo")
public Result<TableMetaData> tableInfo(String sql) {
TableMetaData vo = sqlParserService.tableInfo(sql);
return Result.success(vo);
}
@Operation(summary = "解析SQL成列数据", description = "解析SQL成列数据")
@PostMapping("columnMetaData")
public Result<List<ColumnMetaData>> columnMetaData(String sql) {
ConcreteSqlParserDatabaseInfo databaseInfoCore = new ConcreteSqlParserDatabaseInfo();
List<ColumnMetaData> vo = databaseInfoCore.tableColumnInfo(sql);
return Result.success(vo);
}
}

View File

@ -1,64 +0,0 @@
package cn.bunny.core.factory;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Component
public abstract class AbstractDatabaseInfo {
@Resource
public DataSource dataSource;
/**
* 获取表的所有主键列名
*
* @param tableName 表名
* @return 主键列名的集合
*/
@SneakyThrows
public Set<String> getPrimaryKeyColumns(String tableName) {
// 主键的key
Set<String> primaryKeys = new HashSet<>();
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
// 当前表的主键
ResultSet pkResultSet = metaData.getPrimaryKeys(null, null, tableName);
while (pkResultSet.next()) {
// 列字段
String columnName = pkResultSet.getString("COLUMN_NAME").toLowerCase();
primaryKeys.add(columnName);
}
return primaryKeys;
}
}
/**
* 解析 sql 表信息
*
* @param name 表名称或sql
* @return 表西悉尼
*/
public abstract TableMetaData getTableMetadata(String name);
/**
* 获取当前表的列属性
*
* @param name 表名称或sql
* @return 当前表所有的列内容
*/
public abstract List<ColumnMetaData> tableColumnInfo(String name);
}

View File

@ -1,169 +0,0 @@
package cn.bunny.core.factory;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.DatabaseInfoMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.utils.TypeConvertUtil;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.*;
@Component
public class ConcreteDatabaseInfo extends AbstractDatabaseInfo {
@Value("${bunny.master.database}")
private String currentDatabase;
/**
* 数据库所有的信息
*
* @return 当前连接的数据库信息属性
*/
@SneakyThrows
public DatabaseInfoMetaData databaseInfoMetaData() {
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
return DatabaseInfoMetaData.builder()
.databaseProductName(metaData.getDatabaseProductName())
.databaseProductVersion(metaData.getDatabaseProductVersion())
.driverName(metaData.getDriverName())
.driverVersion(metaData.getDriverVersion())
.url(metaData.getURL())
.username(metaData.getUserName())
.currentDatabase(currentDatabase)
.build();
}
}
/**
* 解析 sql 表信息
*
* @param tableName 表名称或sql
* @return 表西悉尼
*/
@SneakyThrows
@Override
public TableMetaData getTableMetadata(String tableName) {
TableMetaData tableMetaData;
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
ResultSet tables = metaData.getTables(null, null, tableName, new String[]{"TABLE"});
// 获取表的注释信息
if (tables.next()) {
// 备注信息
String remarks = tables.getString("REMARKS");
// 数组名称
String tableCat = tables.getString("TABLE_CAT");
// 通常是"TABLE"
String tableType = tables.getString("TABLE_TYPE");
tableMetaData = TableMetaData.builder()
.tableName(tableName)
.comment(remarks)
.tableCat(tableCat)
.tableType(tableType)
.build();
} else {
throw new RuntimeException("数据表不存在");
}
return tableMetaData;
}
}
/**
* 获取[当前/所有]数据库表
*
* @return 所有表信息
*/
@SneakyThrows
public List<TableMetaData> databaseTableList(String dbName) {
// 当前数据库数据库所有的表
List<TableMetaData> allTableInfo = new ArrayList<>();
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
// 当前数据库中所有的表
ResultSet tables = metaData.getTables(dbName, null, "%", new String[]{"TABLE"});
while (tables.next()) {
// 表名称
dbName = tables.getString("TABLE_NAME");
// 设置表信息
TableMetaData tableMetaData = getTableMetadata(dbName);
allTableInfo.add(tableMetaData);
}
}
return allTableInfo;
}
/**
* 获取当前表的列属性
*
* @param tableName 表名称或sql
* @return 当前表所有的列内容
*/
@SneakyThrows
@Override
public List<ColumnMetaData> tableColumnInfo(String tableName) {
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
Map<String, ColumnMetaData> map = new LinkedHashMap<>();
// 当前表的主键
Set<String> primaryKeyColumns = getPrimaryKeyColumns(tableName);
// 当前表的列信息
try (ResultSet columnsRs = metaData.getColumns(null, null, tableName, null)) {
while (columnsRs.next()) {
ColumnMetaData column = new ColumnMetaData();
// 列字段
String columnName = columnsRs.getString("COLUMN_NAME");
// 数据库类型
String typeName = columnsRs.getString("TYPE_NAME");
// 设置列字段
column.setColumnName(columnName);
// 列字段转成 下划线 -> 小驼峰
column.setLowercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName()));
// 列字段转成 下划线 -> 大驼峰名称
column.setUppercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName(), true));
// 字段类型
column.setJdbcType(typeName);
// 字段类型转 Java 类型
String javaType = TypeConvertUtil.convertToJavaType(typeName);
column.setJavaType(javaType);
// 字段类型转 JavaScript 类型
column.setJavascriptType(StringUtils.uncapitalize(javaType));
// 备注信息
column.setComment(columnsRs.getString("REMARKS"));
// 确保 primaryKeyColumns 不为空
if (!primaryKeyColumns.isEmpty()) {
// 是否是主键
boolean isPrimaryKey = primaryKeyColumns.contains(columnName);
column.setIsPrimaryKey(isPrimaryKey);
}
map.putIfAbsent(column.getColumnName(), column);
}
}
return new ArrayList<>(map.values());
}
}
}

View File

@ -1,62 +0,0 @@
package cn.bunny.core.template;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import org.apache.velocity.VelocityContext;
import java.io.StringWriter;
import java.util.List;
/**
* 模板方法模式
* 如果需要继承 AbstractVmsGenerator
*/
public abstract class AbstractVmsGeneratorTemplate {
/**
* 添加生成内容
*/
abstract void addContext(VelocityContext context);
/**
* Velocity 生成模板
*
* @param context VelocityContext
* @param writer StringWriter 写入
*/
abstract void templateMerge(VelocityContext context, StringWriter writer);
/**
* 生成模板
*
* @param tableMetaData 表属性
* @param columnInfoList 列属性数组
* @return StringWriter
*/
public final StringWriter generatorCodeTemplate(TableMetaData tableMetaData, List<ColumnMetaData> columnInfoList) {
VelocityContext context = new VelocityContext();
// 添加要生成的属性
StringWriter writer = new StringWriter();
List<String> list = columnInfoList.stream().map(ColumnMetaData::getColumnName).distinct().toList();
// vm 不能直接写 `{` 需要转换下
context.put("leftBrace", "{");
// 当前的表名
context.put("tableName", tableMetaData.getTableName());
// 当前表的列信息
context.put("columnInfoList", columnInfoList);
// 数据库sql列
context.put("baseColumnList", String.join(",", list));
// 添加需要生成的内容
addContext(context);
templateMerge(context, writer);
return writer;
}
}

View File

@ -1,53 +0,0 @@
package cn.bunny.domain.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class VmsArgumentDto {
/* 作者名称 */
String author = "Bunny";
/* 包名称 */
String packageName = "cn.bunny.services";
/* requestMapping 名称 */
String requestMapping = "/api";
/* 类名称格式为xxx xxx_xxx */
@NotBlank(message = "类名称不能为空")
@NotNull(message = "类名称不能为空")
@Pattern(regexp = "^(?:[a-z][a-z0-9_]*|[_a-z][a-z0-9_]*)$", message = "类名称不合法")
private String className;
/* 表名称 */
@NotBlank(message = "表名称不能为空")
@NotNull(message = "表名称不能为空")
@Pattern(regexp = "^(?:[a-z][a-z0-9_]*|[_a-z][a-z0-9_]*)$", message = "表名称不合法")
private String tableName;
/* 时间格式 */
private String simpleDateFormat = "yyyy-MM-dd HH:mm:ss";
/* 去除表前缀 */
private String tablePrefixes = "t_,sys_,qrtz_,log_";
/* 注释内容 */
private String comment;
/* 路径 */
private List<String> path;
/* SQL 语句 */
private String sql;
}

View File

@ -1,40 +0,0 @@
package cn.bunny.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DatabaseInfoMetaData {
/* 数据库所有的数据库 */
List<TableMetaData> databaseList;
/* 数据库产品名称 */
private String databaseProductName;
/* 数据库产品版本 */
private String databaseProductVersion;
/* 驱动名称 */
private String driverName;
/* 数据库驱动版本 */
private String driverVersion;
/* 数据链接url */
private String url;
/* 数据库用户 */
private String username;
/* 当前数据库名称 */
private String currentDatabase;
}

View File

@ -1,33 +0,0 @@
package cn.bunny.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TableMetaData {
/* 表名 */
private String tableName;
/* 注释内容 */
private String comment;
/* 表目录 */
private String tableCat;
/* 表类型(通常是"TABLE" */
private String tableType;
/* 类名 */
private String className;
/* 列名称 */
private List<ColumnMetaData> columns;
}

View File

@ -1,25 +0,0 @@
package cn.bunny.domain.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class GeneratorVo {
/* 生成的代码 */
private String code;
/* 表名 */
private String tableName;
/* 注释内容 */
private String comment;
/* 生成类型路径 */
private String path;
}

View File

@ -1,23 +0,0 @@
package cn.bunny.domain.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class VmsPathVo {
/* 路径名称 */
private String name;
/* 显示的label */
private String label;
/* 文件夹最上级目录名称 */
private String type;
}

View File

@ -1,13 +0,0 @@
package cn.bunny.service;
import cn.bunny.domain.entity.TableMetaData;
public interface SqlParserService {
/**
* 解析SQL内容
*
* @param sql Sql语句
* @return 表信息内容
*/
TableMetaData tableInfo(String sql);
}

View File

@ -1,40 +0,0 @@
package cn.bunny.service;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.DatabaseInfoMetaData;
import cn.bunny.domain.entity.TableMetaData;
import java.util.List;
public interface TableService {
/**
* 获取表属性
*
* @param tableName 表名称
* @return 表属性
*/
TableMetaData tableMetaData(String tableName);
/**
* 获取所有数据库
*
* @return 所有表信息
*/
List<TableMetaData> databaseTableList(String tableName);
/**
* 获取列属性
*
* @param tableName 表名称
* @return 当前表所有的列内容
*/
List<ColumnMetaData> tableColumnInfo(String tableName);
/**
* 数据库所有的信息
*
* @return 当前连接的数据库信息属性
*/
DatabaseInfoMetaData databaseInfoMetaData();
}

View File

@ -1,37 +0,0 @@
package cn.bunny.service;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.vo.GeneratorVo;
import cn.bunny.domain.vo.VmsPathVo;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import java.util.List;
import java.util.Map;
public interface VmsService {
/**
* 生成服务端代码
*
* @param dto VmsArgumentDto
* @return 生成内容
*/
List<GeneratorVo> generator(VmsArgumentDto dto);
/**
* 获取vms文件路径
*
* @return vms下的文件路径
*/
Map<String, List<VmsPathVo>> vmsResourcePathList();
/**
* 打包成zip下载
*
* @param dto VmsArgumentDto
* @return zip 文件
*/
ResponseEntity<byte[]> downloadByZip(@Valid VmsArgumentDto dto);
}

View File

@ -1,30 +0,0 @@
package cn.bunny.service.impl;
import cn.bunny.core.factory.ConcreteSqlParserDatabaseInfo;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.service.SqlParserService;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
@Service
public class SqlParserServiceImpl implements SqlParserService {
@Resource
private ConcreteSqlParserDatabaseInfo sqlParserDatabaseInfo;
/**
* 解析SQL内容
*
* @param sql Sql语句
* @return 表信息内容
*/
@Override
public TableMetaData tableInfo(String sql) {
TableMetaData tableInfoVo = new TableMetaData();
TableMetaData tableMetaData = sqlParserDatabaseInfo.getTableMetadata(sql);
BeanUtils.copyProperties(tableMetaData, tableInfoVo);
return tableInfoVo;
}
}

View File

@ -1,94 +0,0 @@
package cn.bunny.service.impl;
import cn.bunny.core.factory.ConcreteDatabaseInfo;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.DatabaseInfoMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.service.TableService;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class TableServiceImpl implements TableService {
@Resource
private ConcreteDatabaseInfo databaseInfoCore;
/**
* 获取表属性
*
* @param tableName 表名称
* @return 表属性
*/
@SneakyThrows
@Override
public TableMetaData tableMetaData(String tableName) {
TableMetaData tableInfoVo = new TableMetaData();
TableMetaData tableMetaData = databaseInfoCore.getTableMetadata(tableName);
BeanUtils.copyProperties(tableMetaData, tableInfoVo);
return tableInfoVo;
}
/**
* 获取[当前/所有]数据库表
*
* @return 所有表信息
*/
@SneakyThrows
@Override
public List<TableMetaData> databaseTableList(String dbName) {
List<TableMetaData> allTableInfo = databaseInfoCore.databaseTableList(dbName);
return allTableInfo.stream().map(tableMetaData -> {
TableMetaData tableInfoVo = new TableMetaData();
BeanUtils.copyProperties(tableMetaData, tableInfoVo);
return tableInfoVo;
}).toList();
}
/**
* 获取当前表的列属性
*
* @param tableName 表名称
* @return 当前表所有的列内容
*/
@SneakyThrows
@Override
public List<ColumnMetaData> tableColumnInfo(String tableName) {
return databaseInfoCore.tableColumnInfo(tableName);
}
/**
* 数据库所有的信息
*
* @return 当前连接的数据库信息属性
*/
@SneakyThrows
@Override
public DatabaseInfoMetaData databaseInfoMetaData() {
List<TableMetaData> databaseTableList = databaseTableList(null);
// 将当前数据库表分组以数据库名称为key
List<TableMetaData> databaseList = databaseTableList.stream()
.collect(Collectors.groupingBy(TableMetaData::getTableCat))
.values().stream()
.map(tableInfoVos -> {
TableMetaData tableInfoVo = tableInfoVos.get(0);
tableInfoVo.setTableName(null);
return tableInfoVo;
}).toList();
DatabaseInfoMetaData databaseInfoMetaData = databaseInfoCore.databaseInfoMetaData();
databaseInfoMetaData.setDatabaseList(databaseList);
return databaseInfoMetaData;
}
}

View File

@ -1,153 +0,0 @@
package cn.bunny.service.impl;
import cn.bunny.core.factory.ConcreteDatabaseInfo;
import cn.bunny.core.factory.ConcreteSqlParserDatabaseInfo;
import cn.bunny.core.template.VmsArgumentDtoBaseVmsGeneratorTemplate;
import cn.bunny.domain.dto.VmsArgumentDto;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.domain.vo.GeneratorVo;
import cn.bunny.domain.vo.VmsPathVo;
import cn.bunny.service.VmsService;
import cn.bunny.utils.ResourceFileUtil;
import cn.bunny.utils.VmsUtil;
import cn.hutool.crypto.digest.MD5;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@Service
public class VmsServiceImpl implements VmsService {
@Resource
ConcreteDatabaseInfo databaseInfoCore;
@Resource
ConcreteSqlParserDatabaseInfo sqlParserDatabaseInfo;
/**
* 生成服务端代码
*
* @param dto VmsArgumentDto
* @return 生成内容
*/
@Override
public List<GeneratorVo> generator(VmsArgumentDto dto) {
String tableName = dto.getTableName();
String sql = dto.getSql();
// 表格属性名 列信息
TableMetaData tableMetaData = StringUtils.hasText(dto.getSql())
? sqlParserDatabaseInfo.getTableMetadata(dto.getSql())
: databaseInfoCore.getTableMetadata(dto.getTableName());
List<ColumnMetaData> columnInfoList = StringUtils.hasText(sql)
? sqlParserDatabaseInfo.tableColumnInfo(sql)
: databaseInfoCore.tableColumnInfo(tableName).stream().distinct().toList();
return dto.getPath().stream().map(path -> {
// 生成模板
VmsArgumentDtoBaseVmsGeneratorTemplate vmsArgumentDtoBaseVmsGenerator = new VmsArgumentDtoBaseVmsGeneratorTemplate(dto, path);
StringWriter writer = vmsArgumentDtoBaseVmsGenerator.generatorCodeTemplate(tableMetaData, columnInfoList);
// 处理 vm 文件名
path = VmsUtil.handleVmFilename(path, dto.getClassName());
return GeneratorVo.builder()
.code(writer.toString())
.comment(tableMetaData.getComment())
.tableName(tableMetaData.getTableName())
.path(path)
.build();
}).toList();
}
/**
* 获取vms文件路径
*
* @return vms下的文件路径
*/
@SneakyThrows
@Override
public Map<String, List<VmsPathVo>> vmsResourcePathList() {
// 读取当前项目中所有的 vm 模板发给前端
List<String> vmsRelativeFiles = ResourceFileUtil.getRelativeFiles("vms");
return vmsRelativeFiles.stream().map(vmFile -> {
String[] filepathList = vmFile.split("/");
String filename = filepathList[filepathList.length - 1].replace(".vm", "");
return VmsPathVo.builder().name(vmFile).label(filename).type(filepathList[0]).build();
})
.collect(Collectors.groupingBy(VmsPathVo::getType));
}
/**
* 打包成zip下载
*
* @param dto VmsArgumentDto
* @return zip 文件
*/
@Override
public ResponseEntity<byte[]> downloadByZip(VmsArgumentDto dto) {
// 需要下载的数据
List<GeneratorVo> generatorVoList = generator(dto);
// 1. 创建临时ZIP文件
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
// 2. 遍历并创建
generatorVoList.forEach(generatorVo -> {
// zip中的路径
String path = generatorVo.getPath().replace(".vm", "");
// zip中的文件
String code = generatorVo.getCode();
ZipEntry zipEntry = new ZipEntry(path);
try {
// 如果有 / 会转成文件夹
zipOutputStream.putNextEntry(zipEntry);
// 写入文件
zipOutputStream.write(code.getBytes(StandardCharsets.UTF_8));
zipOutputStream.closeEntry();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
// 2.1 文件不重名
long currentTimeMillis = System.currentTimeMillis();
String digestHex = MD5.create().digestHex(currentTimeMillis + "");
// 3. 准备响应
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment; filename=" + "vms-" + digestHex + ".zip");
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
return new ResponseEntity<>(byteArrayInputStream.readAllBytes(), headers, HttpStatus.OK);
}
}

View File

@ -1,61 +0,0 @@
package cn.bunny.utils;
import com.google.common.base.CaseFormat;
import java.util.Map;
public class VmsUtil {
private static final Map<String, String> TYPE_MAPPINGS = Map.of(
"controller", "Controller",
"service", "Service",
"serviceImpl", "ServiceImpl",
"mapper", "Mapper",
"resourceMapper", "Mapper",
"dto", "Dto",
"vo", "Vo"
);
/**
* 处理 vm 文件名
*
* @param path 文件路径
* @param className 类名
*/
public static String handleVmFilename(String path, String className) {
String[] splitPaths = path.split("/");
int splitPathsSize = splitPaths.length - 1;
// 大驼峰名称
String CamelCase = TypeConvertUtil.convertToCamelCase(className, true);
// 小驼峰名称
String camelCase = TypeConvertUtil.convertToCamelCase(className);
// 当前文件名
String filename = splitPaths[splitPathsSize];
filename = filename.replace(".vm", "");
String[] split = filename.split("\\.");
// 文件名称
String name = split[0];
// 文件扩展名
String extension = "";
if (split.length >= 2) {
extension = split[1];
}
// 判断是否是 Java 或者 xml 文件
String typeMappingsFilename = TYPE_MAPPINGS.get(name);
typeMappingsFilename = typeMappingsFilename == null ? "" : typeMappingsFilename;
if (filename.contains("java") || filename.contains("xml")) {
filename = CamelCase + typeMappingsFilename + "." + extension;
}
if (filename.contains("vue") && !filename.contains("index")) {
filename = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, camelCase) + "-" + name + "." + extension;
}
splitPaths[splitPathsSize] = filename;
return String.join("/", splitPaths);
}
}

View File

@ -1,11 +0,0 @@
bunny:
master:
host: 192.168.3.137
port: 3306
database: auth_admin
username: root
password: "123456"
# connect:
# url: jdbc:sqlite::resource:database.sqlite
# username: root
# password: "123456"

View File

@ -1,10 +0,0 @@
_ _
| |__ _ _ _ __ _ __ _ _ (_) __ ___ ____ _
| '_ \| | | | '_ \| '_ \| | | | | |/ _` \ \ / / _` |
| |_) | |_| | | | | | | | |_| | | | (_| |\ V | (_| |
|_.__/ \__,_|_| |_|_| |_|\__, | _/ |\__,_| \_/ \__,_|
|___/ |__/
Service Name${spring.application.name}
SpringBoot Version: ${spring-boot.version}${spring-boot.formatted-version}
SpringActive${spring.profiles.active}

View File

@ -1,69 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<contextName>logback</contextName>
<!-- 格式化 年--日 输出 -->
<timestamp key="datetime" datePattern="yyyy-MM-dd"/>
<!--编码-->
<property name="ENCODING" value="UTF-8"/>
<!-- 控制台日志 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- 临界值过滤器 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>%cyan([%thread %d{yyyy-MM-dd HH:mm:ss}]) %yellow(%-5level) %green(%logger{100}).%boldRed(%method)-%boldMagenta(%line)-%blue(%msg%n)
</pattern>
<charset>${ENCODING}</charset>
</encoder>
</appender>
<!-- 文件日志 -->
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/${datetime}/financial-server.log</file>
<append>true</append>
<encoder>
<pattern>%date{yyyy-MM-dd HH:mm:ss} [%-5level] %thread %file:%line %logger %msg%n</pattern>
<charset>${ENCODING}</charset>
</encoder>
</appender>
<!-- 让SpringBoot内部日志ERROR级别 减少日志输出 -->
<logger name="org.springframework" level="ERROR" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<!-- 让mybatis整合包日志ERROR 减少日志输出 -->
<logger name="org.mybatis" level="ERROR" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<!-- 让ibatis 日志ERROR 减少日志输出 -->
<logger name="org.apache.ibatis" level="ERROR" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<!-- 让 tomcat包打印日志 日志ERROR 减少日志输出 -->
<logger name="org.apache" level="ERROR" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<!-- 我们自己开发的程序为DEBUG -->
<logger name="cn.bunny" level="DEBUG" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<logger name="com.baomidou" level="ERROR" additivity="false">
<appender-ref ref="STOUT"/>
</logger>
<!-- 根日志记录器INFO级别 -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>

View File

@ -1,98 +0,0 @@
package cn.bunny;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.utils.TypeConvertUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
public class JDBCTest {
private final DataSource dataSource;
DatabaseMetaData metaData;
public JDBCTest(DataSource dataSource) {
this.dataSource = dataSource;
}
@BeforeEach
public void setUp() throws Exception {
try (Connection connection = dataSource.getConnection()) {
metaData = connection.getMetaData();
}
}
@Test
void testComment() throws SQLException {
String tableName = "sys_i18n";
TableMetaData tableMetaData;
ResultSet tables = metaData.getTables(null, null, tableName, new String[]{"TABLE"});
// 获取表的注释信息
if (tables.next()) {
String remarks = tables.getString("REMARKS");
String tableCat = tables.getString("TABLE_CAT");
tableMetaData = TableMetaData.builder()
.tableName(tableName)
.comment(remarks)
.tableCat(tableCat)
.build();
System.out.println(tableMetaData);
}
}
@Test
void testAllTableComment() throws SQLException {
ResultSet tables = metaData.getTables(null, null, "%", new String[]{"TABLE"});
List<TableMetaData> list = new ArrayList<>();
while (tables.next()) {
String tableName = tables.getString("TABLE_NAME");
String remarks = tables.getString("REMARKS");
String tableCat = tables.getString("TABLE_CAT");
TableMetaData tableMetaData = TableMetaData.builder()
.tableName(tableName).comment(remarks)
.tableCat(tableCat)
.build();
list.add(tableMetaData);
}
System.out.println(list);
}
@Test
void testColumnInfo() throws SQLException {
List<ColumnMetaData> columns = new ArrayList<>();
try (ResultSet columnsRs = metaData.getColumns(null, null, "sys_i18n", null)) {
while (columnsRs.next()) {
ColumnMetaData column = new ColumnMetaData();
column.setColumnName(columnsRs.getString("COLUMN_NAME"));
column.setLowercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName()));
column.setJdbcType(columnsRs.getString("TYPE_NAME"));
column.setJavaType(TypeConvertUtil.convertToJavaType(column.getJdbcType()));
column.setComment(columnsRs.getString("REMARKS"));
columns.add(column);
System.out.println(column);
}
}
System.out.println(columns);
}
}

View File

@ -1,105 +0,0 @@
package cn.bunny;
import cn.bunny.domain.entity.ColumnMetaData;
import cn.bunny.domain.entity.TableMetaData;
import cn.bunny.utils.TypeConvertUtil;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.create.table.CreateTable;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SqlParserTest {
@Test
public void test() throws JSQLParserException {
String sql = """
CREATE TABLE `sys_files` (
`id` bigint NOT NULL COMMENT '文件的唯一标识符自动递增',
`filename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文件的名称',
`filepath` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文件在服务器上的存储路径',
`file_size` int NOT NULL COMMENT '文件的大小以字节为单位',
`file_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文件的MIME类型',
`download_count` int NULL DEFAULT 0 COMMENT '下载数量',
`create_user` bigint NOT NULL COMMENT '创建用户',
`update_user` bigint NULL DEFAULT NULL COMMENT '操作用户',
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录文件最后修改的时间戳',
`is_deleted` tinyint(1) UNSIGNED ZEROFILL NOT NULL DEFAULT 0 COMMENT '文件是否被删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_filename`(`filename` ASC) USING BTREE COMMENT '索引文件名',
INDEX `idx_filepath`(`filepath` ASC) USING BTREE COMMENT '索引文件路径',
INDEX `idx_file_type`(`file_type` ASC) USING BTREE COMMENT '索引文件类型',
INDEX `idx_update_user`(`update_user` ASC) USING BTREE COMMENT '索引创更新用户',
INDEX `idx_create_user`(`create_user` ASC) USING BTREE COMMENT '索引创建用户',
INDEX `idx_user`(`update_user` ASC, `create_user` ASC) USING BTREE COMMENT '索引创建用户和更新用户',
INDEX `idx_time`(`update_time` ASC, `create_time` ASC) USING BTREE COMMENT '索引创建时间和更新时间'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统文件表' ROW_FORMAT = DYNAMIC;
""";
TableMetaData tableInfo = new TableMetaData();
// 解析sql
Statement statement = CCJSqlParserUtil.parse(sql);
if (!(statement instanceof CreateTable createTable)) {
throw new IllegalArgumentException("Not a CREATE TABLE statement");
}
// 设置表基本信息
tableInfo.setTableName(createTable.getTable().getName());
tableInfo.setTableType("TABLE");
String tableOptionsStrings = String.join(" ", createTable.getTableOptionsStrings());
// 注释信息
Pattern pattern = Pattern.compile("COMMENT\\s*=\\s*'(.*?)'", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(tableOptionsStrings);
if (matcher.find()) {
tableInfo.setComment(matcher.group(1));
}
// 解析列信息
List<ColumnMetaData> columnMetaData = createTable.getColumnDefinitions()
.stream().map(column -> {
// 列信息
ColumnMetaData columnInfo = new ColumnMetaData();
// 列名称
columnInfo.setColumnName(column.getColumnName());
// 设置 JDBC 类型
String dataType = column.getColDataType().getDataType();
columnInfo.setJdbcType(dataType);
// 设置 Java 类型
String javaType = TypeConvertUtil.convertToJavaType(dataType.contains("varchar") ? "varchar" : dataType);
columnInfo.setJavaType(javaType);
// 设置 JavaScript 类型
columnInfo.setJavascriptType(StringUtils.uncapitalize(javaType));
// 列字段转成 下划线 -> 小驼峰
columnInfo.setLowercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName()));
// 列字段转成 下划线 -> 大驼峰名称
columnInfo.setUppercaseName(TypeConvertUtil.convertToCamelCase(column.getColumnName(), true));
// 解析注释
List<String> columnSpecs = column.getColumnSpecs();
String columnSpecsString = String.join(" ", columnSpecs);
Matcher columnSpecsStringMatcher = Pattern.compile("COMMENT\\s*'(.*?)'", Pattern.CASE_INSENSITIVE).matcher(columnSpecsString);
if (columnSpecsStringMatcher.find()) {
columnInfo.setComment(columnSpecsStringMatcher.group(1));
}
return columnInfo;
}).toList();
System.out.println(tableInfo);
System.out.println("----------------------------------------------------------------------------------------");
System.out.println(columnMetaData);
}
}

View File

@ -1,43 +0,0 @@
package cn.bunny;
import cn.bunny.utils.TypeConvertUtil;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.util.introspection.CaseFormatUtils;
import org.junit.jupiter.api.Test;
public class StringFormatTest {
@Test
void test1() {
System.out.println(CaseFormatUtils.toCamelCase("user_login"));
System.out.println(CaseFormatUtils.toCamelCase("userLogin"));
System.out.println(CaseFormatUtils.toCamelCase("UserLogin"));
System.out.println("--------------------------------");
System.out.println(StringUtils.lowerCase("user_login"));
System.out.println(StringUtils.lowerCase("userLogin"));
System.out.println(StringUtils.lowerCase("UserLogin"));
System.out.println("--------------------------------");
System.out.println(StringUtils.upperCase("user_login"));
System.out.println(StringUtils.upperCase("userLogin"));
System.out.println(StringUtils.upperCase("UserLogin"));
}
@Test
void test2() {
System.out.println(TypeConvertUtil.convertToCamelCase("user_login_A"));
System.out.println(TypeConvertUtil.convertToCamelCase("User_Login_A"));
System.out.println(TypeConvertUtil.convertToCamelCase("userLoginA"));
System.out.println(TypeConvertUtil.convertToCamelCase("UserLoginA"));
System.out.println("--------------------------------");
System.out.println(TypeConvertUtil.convertToCamelCase("i18n_type_A", true));
System.out.println(TypeConvertUtil.convertToCamelCase("User_Login_A", true));
System.out.println(TypeConvertUtil.convertToCamelCase("userLoginA", true));
System.out.println(TypeConvertUtil.convertToCamelCase("UserLoginA", true));
}
}

View File

@ -1,14 +0,0 @@
package cn.bunny;
import cn.hutool.crypto.digest.MD5;
import org.junit.jupiter.api.Test;
public class TimeTest {
@Test
void timeTest() {
long currentTimeMillis = System.currentTimeMillis();
String digestHex = MD5.create().digestHex(currentTimeMillis + "");
System.out.println(currentTimeMillis);
System.out.println(digestHex);
}
}

View File

@ -1,50 +0,0 @@
package cn.bunny.service.impl;
import cn.bunny.domain.vo.VmsPathVo;
import cn.bunny.utils.ResourceFileUtil;
import com.alibaba.fastjson2.JSON;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class VmsServiceImplTest {
@Test
void vmsResourcePathList() throws IOException, URISyntaxException {
List<String> vmsFiles = ResourceFileUtil.getAbsoluteFiles("vms");
System.out.println(vmsFiles);
System.out.println("--------------------------------------------------------------");
List<String> vmsRelativeFiles = ResourceFileUtil.getRelativeFiles("vms");
System.out.println(vmsRelativeFiles);
System.out.println("--------------------------集合对象模式------------------------------------");
Map<String, List<VmsPathVo>> map = vmsRelativeFiles.stream().map(vmFile -> {
String[] filepathList = vmFile.split("/");
String filename = filepathList[filepathList.length - 1].replace(".vm", "");
return VmsPathVo.builder().name(vmFile).label(filename).type(filepathList[0]).build();
}).collect(Collectors.groupingBy(VmsPathVo::getType));
System.out.println(JSON.toJSONString(map));
System.out.println("----------------------------二维数组格式----------------------------------");
List<List<VmsPathVo>> listMap = vmsRelativeFiles.stream().map(vmFile -> {
String[] filepathList = vmFile.split("/");
String filename = filepathList[filepathList.length - 1].replace(".vm", "");
return VmsPathVo.builder().name(vmFile).label(filename).type(filepathList[0]).build();
})
.collect(Collectors.groupingBy(VmsPathVo::getType))
.values().stream().toList();
System.out.println(JSON.toJSONString(listMap));
}
}

View File

@ -1,75 +0,0 @@
package cn.bunny.utils;
import cn.bunny.domain.entity.TableMetaData;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
class ConcreteDatabaseInfoCoreTest {
String tableName = "sys_i18n";
@Autowired
private DataSource dataSource;
@Test
void testTableInfoMetaData() {
TableMetaData tableMetaData;
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
ResultSet tables = metaData.getTables(null, null, tableName, new String[]{"TABLE"});
// 获取表的注释信息
if (tables.next()) {
String remarks = tables.getString("REMARKS");
String tableCat = tables.getString("TABLE_CAT");
String tableType = tables.getString("TABLE_TYPE");
tableMetaData = TableMetaData.builder()
.tableName(tableName)
.comment(remarks)
.tableCat(tableCat)
.tableType(tableType)
.build();
} else {
throw new RuntimeException("数据表不存在");
}
System.out.println(tableMetaData);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@SneakyThrows
@Test
void getDbTableList() {
String dbName = "auth_admin";
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
ResultSet tables = metaData.getTables(dbName, null, "%", new String[]{"TABLE"});
List<String> list = new ArrayList<>();
while (tables.next()) {
dbName = tables.getString("TABLE_NAME");
list.add(dbName);
}
System.out.println(list);
}
}
}

View File

@ -1,21 +0,0 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
.eslintcache
report.html
yarn.lock
npm-debug.log*
.pnpm-error.log*
.pnpm-debug.log
tests/**/coverage/
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
tsconfig.tsbuildinfo

View File

@ -1,29 +0,0 @@
# 应用名称
VITE_APP_TITLE="代码生成器"
# 平台本地运行端口号
VITE_PORT=7000
# 开发环境读取配置文件路径
VITE_PUBLIC_PATH=/
# 跨域代理地址
VITE_APP_URL=http://localhost:9999
# 如果端口被占用会直接退出,而不是尝试下一个端口
VITE_STRICT_PORT=false
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
VITE_CDN=false
# 是否使用Mock
VITE_MOCK_DEV_SERVER=true
# mock地址
VITE_MOCK_BASE_API=/mock
# 基础请求路径
VITE_APP_BASE_API=/api
# 是否启用gzip压缩
VITE_COMPRESSION=gzip

View File

@ -1,26 +0,0 @@
# 平台本地运行端口号
VITE_PORT=7000
# 开发环境读取配置文件路径
VITE_PUBLIC_PATH=/
# 跨域代理地址
VITE_APP_URL=http://localhost:8800
# 基础请求路径
VITE_APP_BASE_API=/api
# mock地址
VITE_MOCK_BASE_API=/mock
# 是否使用Mock
VITE_MOCK_DEV_SERVER=true
# 如果端口被占用会直接退出,而不是尝试下一个端口
VITE_STRICT_PORT=false
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
VITE_CDN=false
# 是否启用gzip压缩
VITE_COMPRESSION=gzip

View File

@ -1,26 +0,0 @@
# 平台本地运行端口号
VITE_PORT=7000
# 开发环境读取配置文件路径
VITE_PUBLIC_PATH=/
# 跨域代理地址
VITE_APP_URL=http://localhost:8800
# 基础请求路径
VITE_APP_BASE_API=/api
# 是否使用Mock
VITE_MOCK_DEV_SERVER=true
# mock地址
VITE_MOCK_BASE_API=/mock
# 如果端口被占用会直接退出,而不是尝试下一个端口
VITE_STRICT_PORT=false
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
VITE_CDN=false
# 是否启用gzip压缩
VITE_COMPRESSION=gzip

View File

@ -1,11 +0,0 @@
dist
node_modules
public
.husky
.vscode
.idea
*.sh
*.md
src/assets
stats.html

View File

@ -1,46 +0,0 @@
export default {
// (x)=>{},单个参数箭头函数是否显示小括号。(always:始终显示;avoid:省略括号。默认:always)
arrowParens: "always",
// 开始标签的右尖括号是否跟随在最后一行属性末尾默认false
bracketSameLine: false,
// 对象字面量的括号之间打印空格 (true - Example: { foo: bar } ; false - Example: {foo:bar})
bracketSpacing: true,
// 是否格式化一些文件中被嵌入的代码片段的风格(auto|off;默认auto)
embeddedLanguageFormatting: "auto",
// 指定 HTML 文件的空格敏感度 (css|strict|ignore;默认css)
htmlWhitespaceSensitivity: "ignore",
// 当文件已经被 Prettier 格式化之后,是否会在文件顶部插入一个特殊的 @format 标记默认false
insertPragma: false,
// 在 JSX 中使用单引号替代双引号默认false
jsxSingleQuote: false,
// 每行最多字符数量,超出换行(默认100)
printWidth: 100,
// 超出打印宽度 (always | never | preserve )
proseWrap: "preserve",
// 对象属性是否使用引号(as-needed | consistent | preserve;默认as-needed:对象的属性需要加引号才添加;)
quoteProps: "as-needed",
// 是否只格式化在文件顶部包含特定注释(@prettier| @format)的文件默认false
requirePragma: false,
// 结尾添加分号
semi: true,
// 使用单引号 (true:单引号;false:双引号)
singleQuote: true,
// 缩进空格数默认2个空格
tabWidth: 2,
// 元素末尾是否加逗号默认es5: ES5中的 objects, arrays 等会添加逗号TypeScript 中的 type 后不加逗号
trailingComma: "es5",
// 指定缩进方式空格或tab默认false即使用空格
useTabs: false,
// vue 文件中是否缩进 <style> 和 <script> 标签,默认 false
vueIndentScriptAndStyle: false,
endOfLine: "auto",
overrides: [
{
files: "*.html",
options: {
parser: "html",
},
},
],
};

View File

@ -1,11 +0,0 @@
dist
node_modules
public
.husky
.vscode
.idea
*.sh
*.md
src/assets
stats.html

View File

@ -1,8 +0,0 @@
{
"hash": "727e0168",
"configHash": "10198a12",
"lockfileHash": "530f8857",
"browserHash": "41b39cb9",
"optimized": {},
"chunks": {}
}

View File

@ -1,3 +0,0 @@
{
"type": "module"
}

View File

@ -1,53 +0,0 @@
import type { BuildOptions } from 'vite';
import { pathResolve } from './utils';
export const buildEnv = (): BuildOptions => {
return {
target: 'es2015',
assetsInlineLimit: 20000,
// 构建输出的目录,默认值为"dist"
outDir: 'docker/dist',
// 用于指定使用的代码压缩工具。在这里minify 被设置为 'terser',表示使用 Terser 进行代码压缩。默认值terser
// esbuild 打包更快,但是不能去除 console.logterser打包慢但能去除 console.log
minify: 'terser', // "esbuild"
// 用于配置 Terser 的选项
terserOptions: {
// 用于配置压缩选项
compress: {
drop_console: true, // 是否删除代码中的 console 语句, 默认值false
drop_debugger: true, // 是否删除代码中的 debugger 语句, 默认值false
},
},
// 禁用 gzip 压缩大小报告,可略微减少打包时间
reportCompressedSize: false,
// 用于指定是否生成源映射文件。源映射文件可以帮助调试和定位源代码中的错误。当设置为false时构建过程不会生成源映射文件
sourcemap: false,
// 用于配置 CommonJS 模块的选项
commonjsOptions: {
// 用于指定是否忽略 CommonJS 模块中的 try-catch 语句。当设置为false时构建过程会保留 CommonJS 模块中的 try-catch 语句
ignoreTryCatch: false,
},
// 规定触发警告的 chunk 大小, 当某个代码分块的大小超过该限制时Vite 会发出警告
chunkSizeWarningLimit: 2000,
rollupOptions: {
external: ['md-editor-v3', 'echarts'],
input: {
// @ts-ignore
index: pathResolve('../index.html', import.meta.url),
},
// 静态资源分类打包
output: {
chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
manualChunks: (id) => {
// 如果是包含在包中则打包成 vendor
if (id.includes('node_modules')) {
return `vendor`;
}
},
},
},
};
};

View File

@ -1,47 +0,0 @@
import { Plugin as importToCDN } from 'vite-plugin-cdn-import';
import { wrapperEnv } from './utils';
/**
* @description `cdn`使cdn模式 .env.production VITE_CDN true
* cdnhttps://www.bootcdn.cn当然你也可以选择 https://unpkg.com 或者 https://www.jsdelivr.com
* 使jscss文件cdn
*/
export const cdn = importToCDN({
//prodUrl解释 name: 对应下面modules的nameversion: 自动读取本地package.json中dependencies依赖中对应包的版本号path: 对应下面modules的path当然也可写完整路径会替换prodUrl
// prodUrl: 'https://cdn.bootcdn.net/ajax/libs/{name}/{version}/{path}',
prodUrl: 'https://unpkg.com/{name}@{version}/{path}',
modules: [
{
name: 'vue',
var: 'Vue',
path: 'dist/vue.global.prod.js',
},
{
name: 'vue-router',
var: 'VueRouter',
path: 'dist/vue-router.global.js',
},
{
name: 'pinia',
var: 'Pinia',
path: 'dist/pinia.iife.js',
},
{
name: 'axios',
var: 'axios',
path: 'dist/axios.min.js',
},
{
name: 'dayjs',
var: 'dayjs',
path: 'dayjs.min.js',
},
],
});
/* 是否使用CDN加速 */
export const useCDN = (mode) => {
const env = wrapperEnv(mode, 'VITE');
return env.VITE_CDN ? cdn : null;
};

View File

@ -1,12 +0,0 @@
import type { CSSOptions } from 'vite';
export const css = (mode: string): CSSOptions => {
return {
preprocessorOptions: {
scss: {
additionalData: `@use "@/assets/styles/minix/sidebar" as *;`,
},
},
};
};

View File

@ -1,14 +0,0 @@
import dayjs from 'dayjs';
import { dependencies, devDependencies, engines, name, version } from '../package.json';
const __APP_INFO__ = {
pkg: { name, version, engines, dependencies, devDependencies },
lastBuildTime: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'),
};
export const define = () => {
return {
__APP_INFO__: JSON.stringify(__APP_INFO__),
};
};

View File

@ -1,58 +0,0 @@
import boxen, { type Options as BoxenOptions } from 'boxen';
import dayjs, { type Dayjs } from 'dayjs';
import duration from 'dayjs/plugin/duration';
import gradientString from 'gradient-string';
import { logOutputSize, wrapperEnv } from './utils';
dayjs.extend(duration);
const boxenOptions: BoxenOptions = {
padding: 0.94,
borderColor: 'cyan',
borderStyle: 'round',
textAlignment: 'left',
};
/* 输出日志信息 */
const printLogMessage = (VITE_PORT: number) => {
return gradientString('cyan', 'magenta').multiline(
`欢迎使用此项目,项目访问地址如下:
http://localhost:${VITE_PORT}`
);
};
export const viteConsoleLog = (mode: string) => {
const { VITE_PORT } = wrapperEnv(mode);
let config: { command: string };
let startTime: Dayjs;
let endTime: Dayjs;
return {
name: 'vite:buildInfo',
configResolved(resolvedConfig) {
config = resolvedConfig;
},
buildStart() {
console.log(boxen(printLogMessage(VITE_PORT), boxenOptions));
if (config.command === 'build') {
startTime = dayjs(new Date());
}
},
closeBundle() {
if (config.command === 'build') {
endTime = dayjs(new Date());
const format = dayjs.duration(endTime.diff(startTime)).format('mm分ss秒');
console.log(
boxen(
gradientString('cyan', 'magenta').multiline(
`🎉 恭喜打包完成(总用时${format})打包大小(${logOutputSize()}`
),
boxenOptions
)
);
}
},
};
};

View File

@ -1,14 +0,0 @@
/**
* `vite.config.ts` `optimizeDeps.include`
* `vite` include esm node_modules/.vite
* include里vite 使 node_modules/.vite
* 使 src/main.ts include vite node_modules/.vite
*/
const include = ['vue', 'vue-router', 'dayjs', 'axios', 'pinia', 'vue-types', 'js-cookie'];
/**
*
*/
const exclude: string[] = [];
export { exclude, include };

View File

@ -1,45 +0,0 @@
import UnoCssIcons from '@unocss/preset-icons';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import { presetIcons } from 'unocss';
import UnoCSS from 'unocss/vite';
import type { PluginOption } from 'vite';
import removeConsole from 'vite-plugin-remove-console';
import Inspector from 'vite-plugin-vue-inspector';
import { useCDN } from './cdn';
import { viteConsoleLog } from './info';
import { compressPack, report } from './utils';
export const plugins = (mode: string): PluginOption[] => {
return [
vue(),
vueJsx(),
Inspector(),
report(),
removeConsole(),
useCDN(mode),
viteConsoleLog(mode),
UnoCSS({
hmrTopLevelAwait: false,
inspector: true, // 控制台是否打印 UnoCSS inspector
presets: [
presetIcons({
prefix: '',
extraProperties: {
display: 'inline-block',
'vertical-align': 'middle',
},
}),
UnoCssIcons({
prefix: '',
extraProperties: {
display: 'inline-block',
'vertical-align': 'middle',
},
}),
],
}),
compressPack(mode),
];
};

View File

@ -1,9 +0,0 @@
import { pathResolve } from './utils';
export const resolve = () => {
return {
alias: {
'@': pathResolve('../src'),
},
};
};

View File

@ -1,34 +0,0 @@
import type { ServerOptions } from 'vite';
import { wrapperEnv } from './utils';
/* 开发服务配置 */
export const server = (mode: string) => {
const { VITE_PORT, VITE_APP_URL, VITE_STRICT_PORT } = wrapperEnv(mode);
const options: ServerOptions = {
strictPort: VITE_STRICT_PORT,
port: VITE_PORT,
host: '0.0.0.0',
open: true,
cors: true,
proxy: {
'/api': {
target: VITE_APP_URL,
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/api/, '/api'),
},
'/mock': {
target: VITE_APP_URL,
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/mock/, '/mock'),
},
},
// 预热文件以提前转换和缓存结果,降低启动期间的初始页面加载时长并防止转换瀑布
warmup: {
clientFiles: ['./index.html', './src/{views,components}/*'],
},
};
return options;
};

View File

@ -1,118 +0,0 @@
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import fs from 'fs';
import path from 'path';
import { visualizer } from 'rollup-plugin-visualizer';
import { loadEnv } from 'vite';
import viteCompression from 'vite-plugin-compression';
import { buildEnv } from './buildEnv';
export const root: string = process.cwd();
/**
* @description
* @param dir `build`
* @param metaUrl `url``build``import.meta.url`
*/
// @ts-ignore
export const pathResolve = (dir = '.', metaUrl = import.meta.url) => {
// 当前文件目录的绝对路径
const currentFileDir = dirname(fileURLToPath(metaUrl));
// build 目录的绝对路径
const buildDir = resolve(currentFileDir, 'build');
// 解析的绝对路径
const resolvedPath = resolve(currentFileDir, dir);
// 检查解析的绝对路径是否在 build 目录内
if (resolvedPath.startsWith(buildDir)) {
// 在 build 目录内,返回当前文件路径
return fileURLToPath(metaUrl);
}
// 不在 build 目录内,返回解析后的绝对路径
return resolvedPath;
};
/**
*
* @param mode
* @param prefix
* @link https://cn.vite.dev/config/#using-environment-variables-in-config
*/
// @ts-ignore
export const wrapperEnv = (mode: string, prefix: string = ''): ViteEnv => {
const env: any = loadEnv(mode, root, prefix);
// 将变量转换指定类型
for (const envName of Object.keys(env)) {
let realName: string | boolean | number = env[envName].replace(/\\n/g, '\n');
realName = realName === 'true' ? true : realName === 'false' ? false : realName;
if (envName === 'VITE_PORT') {
realName = Number(realName);
}
env[envName] = realName;
// @ts-ignore
process.env[envName] = realName;
}
return env;
};
/* 打包分析 */
export const report = () => {
const lifecycle = process.env.npm_lifecycle_event;
return lifecycle === 'report'
? visualizer({ open: true, brotliSize: true, filename: 'report.html' })
: (null as any);
};
/* 启用gzip压缩 */
export const compressPack = (mode: string) => {
const { VITE_COMPRESSION } = wrapperEnv(mode);
return VITE_COMPRESSION == 'gzip' ? viteCompression({ threshold: 1024000 }) : null;
};
/**
*
* @returns
*/
export const logOutputSize = (): string => {
const outDir = `../${buildEnv().outDir}`;
function convertSize(size: number) {
const units: Array<string> = ['byte', 'KB', 'MB', 'GB'];
// 输入的单位是否存在
let index = 0;
while (size >= 1024) {
size /= 1024;
index++;
}
return `${size.toFixed(2)} ${units[index]}`;
}
// 计算文件夹字节大小
function getFolderSize(folderPath: string) {
let size = 0;
fs.readdirSync(folderPath).forEach((fileName: string) => {
const filePath = path.join(folderPath, fileName);
const stats = fs.statSync(filePath);
if (stats.isFile()) {
size += stats.size;
} else if (stats.isDirectory()) {
size += getFolderSize(filePath);
}
});
return size;
}
const folderSize = getFolderSize(path.resolve(__dirname, outDir));
return convertSize(folderSize);
};

View File

@ -1,31 +0,0 @@
# 使用官方的 Nginx 镜像作为基础镜像
FROM nginx:1.27.3
# 删除默认的 Nginx 配置文件
RUN rm /etc/nginx/conf.d/default.conf
# 将自定义的 Nginx 配置文件复制到容器中
COPY nginx.conf /etc/nginx/conf.d/default.conf
#COPY bunny-web.site.csr /etc/nginx/bunny-web.site.csr
#COPY bunny-web.site.key /etc/nginx/bunny-web.site.key
#COPY bunny-web.site_bundle.crt /etc/nginx/bunny-web.site_bundle.crt
#COPY bunny-web.site_bundle.pem /etc/nginx/bunny-web.site_bundle.pem
# 设置时区,构建镜像时执行的命令
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo "Asia/Shanghai" > /etc/timezone
# 创建一个目录来存放前端项目文件
WORKDIR /usr/share/nginx/html
# 将前端项目打包文件复制到 Nginx 的默认静态文件目录
COPY dist/ /usr/share/nginx/html
# 复制到nginx目录下
COPY dist/ /etc/nginx/html
# 暴露 Nginx 的默认端口
EXPOSE 80
#EXPOSE 443
# 自动启动 Nginx
CMD ["nginx", "-g", "daemon off;"]

View File

@ -1,32 +0,0 @@
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80 ;
listen [::]:80;
server_name localhost;
client_max_body_size 5M; # 最大文件上传设置
location / {
root /etc/nginx/html;
index index.html index.htm;
try_files $uri /index.html;
}
# 后端跨域请求
location ~/api/ {
proxy_pass http://172.17.0.1:8000;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
error_page 404 404.html;
location = /50x.html {
root html;
}
}

View File

@ -1,177 +0,0 @@
import js from '@eslint/js';
import pluginTypeScript from '@typescript-eslint/eslint-plugin';
import * as parserTypeScript from '@typescript-eslint/parser';
import configPrettier from 'eslint-config-prettier';
import { defineFlatConfig } from 'eslint-define-config';
import pluginPrettier from 'eslint-plugin-prettier';
import eslintPluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
import pluginVue from 'eslint-plugin-vue';
import * as parserVue from 'vue-eslint-parser';
export default defineFlatConfig([
{
...js.configs.recommended,
ignores: ['**/.*', 'dist/*', '*.d.ts', 'public/*', 'src/assets/**', 'src/**/iconfont/**'],
languageOptions: {
globals: {
// index.d.ts
RefType: 'readonly',
EmitType: 'readonly',
TargetContext: 'readonly',
ComponentRef: 'readonly',
ElRef: 'readonly',
ForDataType: 'readonly',
AnyFunction: 'readonly',
PropType: 'readonly',
Writable: 'readonly',
Nullable: 'readonly',
NonNullable: 'readonly',
Recordable: 'readonly',
ReadonlyRecordable: 'readonly',
Indexable: 'readonly',
DeepPartial: 'readonly',
Without: 'readonly',
Exclusive: 'readonly',
TimeoutHandle: 'readonly',
IntervalHandle: 'readonly',
Effect: 'readonly',
ChangeEvent: 'readonly',
WheelEvent: 'readonly',
ImportMetaEnv: 'readonly',
Fn: 'readonly',
PromiseFn: 'readonly',
ComponentElRef: 'readonly',
parseInt: 'readonly',
parseFloat: 'readonly',
},
},
plugins: {
prettier: pluginPrettier,
'simple-import-sort': eslintPluginSimpleImportSort,
},
rules: {
...configPrettier.rules,
...pluginPrettier.configs.recommended.rules,
'simple-import-sort/imports': 'error',
'no-debugger': 'off',
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
},
},
{
files: ['**/*.?([cm])ts', '**/*.?([cm])tsx'],
languageOptions: {
parser: parserTypeScript,
parserOptions: {
sourceType: 'module',
},
},
plugins: {
'@typescript-eslint': pluginTypeScript,
},
rules: {
...pluginTypeScript.configs.strict.rules,
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-redeclare': 'error',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/prefer-as-const': 'warn',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-import-type-side-effects': 'error',
'@typescript-eslint/prefer-literal-enum-member': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/consistent-type-imports': [
'error',
{
disallowTypeAnnotations: false,
fixStyle: 'inline-type-imports',
},
],
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
},
},
{
files: ['**/*.d.ts'],
rules: {
'eslint-comments/no-unlimited-disable': 'off',
'import/no-duplicates': 'off',
'unused-imports/no-unused-vars': 'off',
},
},
{
files: ['**/*.?([cm])js'],
rules: {
'@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/no-var-requires': 'off',
},
},
{
files: ['**/*.vue'],
languageOptions: {
globals: {
$: 'readonly',
$$: 'readonly',
$computed: 'readonly',
$customRef: 'readonly',
$ref: 'readonly',
$shallowRef: 'readonly',
$toRef: 'readonly',
},
parser: parserVue,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
extraFileExtensions: ['.vue'],
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
},
plugins: {
vue: pluginVue,
},
processor: pluginVue.processors['.vue'],
rules: {
...pluginVue.configs.base.rules,
...pluginVue.configs['vue3-essential'].rules,
...pluginVue.configs['vue3-recommended'].rules,
'no-undef': 'off',
'no-unused-vars': 'off',
'vue/no-v-html': 'off',
'vue/require-default-prop': 'off',
'vue/require-explicit-emits': 'off',
'vue/multi-word-component-names': 'off',
'vue/no-setup-props-reactivity-loss': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'always',
component: 'always',
},
svg: 'always',
math: 'always',
},
],
},
},
]);

View File

@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<link href="/favicon.ico" rel="icon" type="image/svg+xml"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>%VITE_APP_TITLE%</title>
</head>
<body>
<div id="app"></div>
<script src="/src/main.ts" type="module"></script>
</body>
</html>

View File

@ -1,93 +0,0 @@
{
"name": "generator-code-web",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"report": "rimraf dist && vite build",
"lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache-location node_modules/.cache/stylelint/",
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint"
},
"dependencies": {
"@eslint/js": "^9.21.0",
"@highlightjs/vue-plugin": "^2.1.0",
"@types/node": "^22.13.10",
"@typescript-eslint/eslint-plugin": "^8.24.1",
"@typescript-eslint/parser": "^8.24.1",
"@unocss/preset-icons": "^66.0.0",
"@unocss/reset": "^66.0.0",
"@vicons/ionicons5": "^0.13.0",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"animate.css": "^4.1.1",
"axios": "^1.7.9",
"boxen": "^8.0.1",
"dayjs": "^1.11.13",
"esbuild": "^0.25.1",
"eslint": "^9.9.1",
"eslint-config-prettier": "^9.1.0",
"eslint-define-config": "^2.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-vue": "^9.27.0",
"gradient-string": "^3.0.0",
"highlight.js": "^11.11.1",
"naive-ui": "^2.41.0",
"nprogress": "^0.2.0",
"pinia": "^2.3.1",
"pinia-plugin-persistedstate": "^3.2.3",
"postcss": "^8.5.3",
"prettier": "^3.3.3",
"qs": "^6.14.0",
"rimraf": "^5.0.10",
"rollup-plugin-visualizer": "^5.14.0",
"sass": "^1.77.8",
"stylelint": "^16.14.1",
"stylelint-config-recess-order": "^6.0.0",
"stylelint-config-recommended-vue": "^1.6.0",
"stylelint-config-standard-scss": "^14.0.0",
"stylelint-prettier": "^5.0.3",
"terser": "^5.39.0",
"unocss": "^66.0.0",
"vite-plugin-cdn-import": "^1.0.1",
"vite-plugin-remove-console": "^2.2.0",
"vite-plugin-vue-inspector": "^5.3.1",
"vue": "^3.5.13",
"vue-demi": "^0.14.10",
"vue-eslint-parser": "^9.4.3",
"vue-router": "^4.4.3",
"vue-types": "^6.0.0"
},
"devDependencies": {
"@iconify/json": "^2.2.310",
"@types/nprogress": "^0.2.3",
"@types/qs": "^6.9.18",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.7.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"typescript": "~5.7.2",
"vite": "^6.1.0",
"vite-plugin-compression": "^0.5.1",
"vue-tsc": "^2.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0",
"pnpm": ">=8.6.10"
},
"pnpm": {
"allowedDeprecatedVersions": {
"sourcemap-codec": "*",
"domexception": "*",
"w3c-hr-time": "*",
"stable": "*",
"abab": "*"
},
"peerDependencyRules": {
"allowedVersions": {
"eslint": "9"
}
}
},
"packageManager": "pnpm@10.8.1+sha512.c50088ba998c67b8ca8c99df8a5e02fd2ae2e2b29aaf238feaa9e124248d3f48f9fb6db2424949ff901cffbb5e0f0cc1ad6aedb602cd29450751d11c35023677"
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

View File

@ -1,19 +0,0 @@
<template>
<n-config-provider>
<n-message-provider>
<content />
<n-dialog-provider>
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade-transform'" mode="out-in">
<component :is="Component" :key="route.path" />
</transition>
</router-view>
</n-dialog-provider>
</n-message-provider>
</n-config-provider>
</template>
<script lang="ts" setup>
import { NConfigProvider, NDialogProvider, NMessageProvider } from 'naive-ui';
import Content from '@/views/content.vue';
</script>

View File

@ -1,64 +0,0 @@
import axios, { type AxiosResponse, type InternalAxiosRequestConfig } from 'axios';
import qs from 'qs';
import { TOKEN_KEY } from '@/enums/CacheEnum';
// 创建 axios 实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 50000,
headers: { 'Content-Type': 'application/json;charset=utf-8' },
paramsSerializer: (params) => {
return qs.stringify(params);
},
});
// 请求拦截器
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const accessToken = localStorage.getItem(TOKEN_KEY);
if (accessToken) {
config.headers.Authorization = accessToken;
}
return config;
},
(error: any) => {
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
// 检查配置的响应类型是否为二进制类型('blob' 或 'arraybuffer', 如果是,直接返回响应对象
if (response.config.responseType === 'blob' || response.config.responseType === 'arraybuffer') {
return response;
}
if (response.status === 200) {
const { code, message } = response.data;
if (code !== 200) {
(window as any).$message.error(message);
}
return response.data;
}
// 系统出错
return Promise.reject(response.data.message || 'Error');
},
(error: any) => {
// 异常处理
if (error.response.data) {
const { code, message } = error.response.data;
if (code === 500) {
(window as any).$message.error(message);
} else {
(window as any).$message.error(message || '系统出错');
}
}
return Promise.reject(error.message);
}
);
// 导出 axios 实例
export default service;

View File

@ -1,68 +0,0 @@
import axios, { type AxiosResponse, type InternalAxiosRequestConfig } from 'axios';
import qs from 'qs';
import { TOKEN_KEY } from '@/enums/CacheEnum';
// 创建 axios 实例
const service = axios.create({
baseURL: import.meta.env.VITE_MOCK_BASE_API,
timeout: 50000,
headers: { 'Content-Type': 'application/json;charset=utf-8' },
paramsSerializer: (params) => {
return qs.stringify(params);
},
});
// 请求拦截器
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const accessToken = localStorage.getItem(TOKEN_KEY);
if (accessToken) {
config.headers.Authorization = accessToken;
}
return config;
},
(error: any) => {
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
// 检查配置的响应类型是否为二进制类型('blob' 或 'arraybuffer', 如果是,直接返回响应对象
if (response.config.responseType === 'blob' || response.config.responseType === 'arraybuffer') {
return response;
}
// const { code, data, msg } = response.data;
// if (code === ResultEnum.SUCCESS) {
// return data;
// }
if (response.status === 200) {
return response.data;
}
// ElMessage.error(msg || '系统出错');
return Promise.reject(response.data.message || 'Error');
},
(error: any) => {
// 异常处理
if (error.response.data) {
// const { code, msg } = error.response.data;
// if (code === ResultEnum.TOKEN_INVALID) {
// ElNotification({
// title: '提示',
// message: '您的会话已过期,请重新登录',
// type: 'info',
// });
// } else {
// ElMessage.error(msg || '系统出错');
// }
}
return Promise.reject(error.message);
}
);
// 导出 axios 实例
export default service;

View File

@ -1,20 +0,0 @@
import request from '@/api/server/request';
import type { BaseResult } from '@/types/request';
/* 当前数据库信息 */
export const fetchTableInfo = (params: any) => {
return request<any, BaseResult<any>>({
url: '/sqlParser/tableInfo',
method: 'POST',
params,
});
};
/* 当前数据库信息 */
export const fetchColumnMetaData = (params: any) => {
return request<any, BaseResult<any>>({
url: '/sqlParser/columnMetaData',
method: 'POST',
params,
});
};

View File

@ -1,22 +0,0 @@
import request from '@/api/server/request';
import type { BaseResult } from '@/types/request';
/* 当前数据库信息 */
export const getDatabaseInfoMetaData = () => {
return request<any, BaseResult<any>>({ url: '/table/databaseInfoMetaData', method: 'GET' });
};
/* 数据库所有的表 */
export const getDatabaseTableList = (params: any) => {
return request<any, BaseResult<any>>({ url: '/table/databaseTableList', method: 'get', params });
};
/* 获取表属性 */
export const getTableMetaData = (params: object) => {
return request<any, BaseResult<any>>({ url: '/table/tableMetaData', method: 'get', params });
};
/* 获取列属性 */
export const getTableColumnInfo = (params: object) => {
return request<any, BaseResult<any>>({ url: '/table/tableColumnInfo', method: 'get', params });
};

View File

@ -1,22 +0,0 @@
import request from '@/api/server/request';
import type { BaseResult } from '@/types/request'; /* 获取所有数据表 */
/* 获取所有数据表 */
export const generator = (data: any) => {
return request<any, BaseResult<any>>({ url: '/vms/generator', method: 'post', data });
};
/* 获取vms文件路径 */
export const getVmsResourcePathList = () => {
return request<any, BaseResult<any>>({ url: '/vms/vmsResourcePathList', method: 'get' });
};
/* 打包成zip下载 */
export const downloadByZip = (data: any) => {
return request<any, any>({
url: '/vms/downloadByZip',
method: 'POST',
data,
responseType: 'blob',
});
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1,2 +0,0 @@
@use "src/rotate";
@use "src/transition";

View File

@ -1,19 +0,0 @@
/* 旋转动画 */
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* 反向旋转动画 */
@keyframes rotate-reverse {
from {
transform: rotate(0deg);
}
to {
transform: rotate(-360deg);
}
}

View File

@ -1,66 +0,0 @@
/* fade */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.28s;
}
.fade-enter,
.fade-leave-active {
opacity: 0;
}
/* fade-transform */
.fade-transform-leave-active,
.fade-transform-enter-active {
transition: all 0.38s;
}
.fade-transform-enter-from {
opacity: 0;
transform: translateX(-30px);
}
.fade-transform-leave-to {
opacity: 0;
transform: translateX(30px);
}
/* breadcrumb transition */
.breadcrumb-enter-active {
transition: all 0.4s;
}
.breadcrumb-leave-active {
position: absolute;
transition: all 0.3s;
}
.breadcrumb-enter-from,
.breadcrumb-leave-active {
opacity: 0;
transform: translateX(20px);
}
/**
* @description 重置el-menu的展开收起动画时长
*/
.outer-most .el-collapse-transition-leave-active,
.outer-most .el-collapse-transition-enter-active {
transition: 0.2s all ease-in-out !important;
}
.horizontal-collapse-transition {
transition: var(--pure-transition-duration) all !important;
}
.slide-enter-active,
.slide-leave-active {
transition: opacity 0.3s,
transform 0.3s;
}
.slide-enter-from,
.slide-leave-to {
opacity: 0;
transform: translateX(-30%);
}

View File

@ -1,22 +0,0 @@
@use "src/element";
/* 定义滚动条高宽及背景高宽分别对应横竖滚动条的尺寸 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
background-color: var(--el-text-color-secondary);
}
/* 定义滚动条轨道内阴影+圆角 */
::-webkit-scrollbar-track {
background-color: #ebecef;
border-radius: 5px;
box-shadow: inset 0 0 6px #ebecef;
}
/* 定义滑块内阴影+圆角 */
::-webkit-scrollbar-thumb {
background-color: #d0d2d6;
border-radius: 5px;
box-shadow: inset 0 0 6px #d0d2d6;
}

View File

@ -1,19 +0,0 @@
// 空心的虚线圆
.dashed-circle {
width: 46px; /* 圆圈的宽度 */
height: 46px; /* 圆圈的高度 */
line-height: 40px;
font-size: 16px;
text-align: center;
border: 2px dashed; /* 边框宽度、样式和颜色 */
border-radius: 50%; /* 将元素变成圆形 */
}
.hover {
transition: all 0.3s;
cursor: pointer;
&:hover {
color: var(--color-hover);
}
}

View File

@ -1,12 +0,0 @@
@use "animations/animations";
@use "common/common";
:root {
--colot-primary: #027AFF;
--color-primary-secondary: #00FFFF;
--color-info: #7CC1FF;
--color-hover: #1C8ADF;
--color-warning: #FFBE44;
--color-warning-secondary: #FEDB65;
--background-color: #051135;
}

View File

@ -1,50 +0,0 @@
@mixin view-style-default($sidebar-width,$content-width) {
&__sidebar {
display: flex;
flex-wrap: wrap;
flex-direction: column;
justify-content: space-between;
align-items: center;
width: $sidebar-width;
height: 100%;
color: #fff;
&-item {
padding: 9px 15px;
width: 100%;
background: rgba(14, 95, 255, 0.2);
}
&-tag {
float: left;
margin: 0 7px 0 0;
width: 62px;
height: 26px;
line-height: 26px;
font-size: 14px;
text-align: center;
background: rgba(24, 69, 135, 0.55);
color: #fff;
cursor: default;
}
&-title {
width: 172px;
height: 42px;
font-size: 22px;
color: #fff;
background: url('@/assets/images/business-supervision/bg/sidebar/bg-frame-4.png') no-repeat;
background-size: cover;
}
&-title-describe {
font-size: 12px;
color: var(--color-info-secondary-1);
}
}
&__content {
width: $content-width;
height: 100%;
}
}

View File

@ -1,49 +0,0 @@
<script lang="ts" setup>
import { NButton, NButtonGroup } from 'naive-ui';
const props = defineProps({
// `id`
data: {
type: Array,
required: true,
},
//
selected: {
type: Array,
default: () => [],
},
// `id`
idKey: {
type: String,
default: 'name',
},
});
const emit = defineEmits(['update:selected']);
/* 全选 */
const selectAll = () => {
const allNames = props.data.map((item) => item[props.idKey]);
emit('update:selected', [...allNames]);
};
/* 反选 */
const selectInvert = () => {
const currentSelected = props.selected;
const allNames = props.data.map((item) => item[props.idKey]);
const inverted = allNames.filter((name) => !currentSelected.includes(name));
emit('update:selected', inverted);
};
/* 全不选 */
const selectCancel = () => {
emit('update:selected', []);
};
</script>
<template>
<n-button-group size="small">
<n-button round type="primary" @click="selectAll">全选</n-button>
<n-button type="warning" @click="selectInvert">反选</n-button>
<n-button round type="error" @click="selectCancel">全不选</n-button>
</n-button-group>
</template>

View File

@ -1,4 +0,0 @@
/**
* Key
*/
export const TOKEN_KEY = 'accessToken';

View File

@ -1,9 +0,0 @@
/** 响应码枚举 */
export const enum ResultEnum {
// 成功
SUCCESS = '200',
// 错误
ERROR = 'B0001',
// 令牌无效或过期
TOKEN_INVALID = '209',
}

View File

@ -1,15 +0,0 @@
<script lang="ts" setup></script>
<template>
<div class="container m-auto">
<h1 class="mt-4 text-center font-bold font-size-[22px] c-primary">代码生成器</h1>
<main class="container mx-auto">
<router-view />
</main>
<footer class="my-4 text-center">
<p>&copy; 2025 Bunny.保留所有权利.</p>
</footer>
</div>
</template>

View File

@ -1,15 +0,0 @@
import 'animate.css';
import '@unocss/reset/tailwind-compat.css';
import 'uno.css';
import 'virtual:unocss-devtools';
import '@/assets/styles/global.scss';
import { createApp } from 'vue';
import plugins from '@/plugins';
import App from './App.vue';
const app = createApp(App);
app.use(plugins).mount('#app');

View File

@ -1,9 +0,0 @@
import 'highlight.js/styles/atom-one-dark.css';
import 'highlight.js/lib/common';
import highlightJSPlugin from '@highlightjs/vue-plugin';
import type { App } from 'vue';
export const setupHighLight = (app: App<Element>) => {
app.use(highlightJSPlugin);
};

View File

@ -1,16 +0,0 @@
import type { App } from 'vue';
import { setupHighLight } from '@/plugins/highLight';
import { setupRouter } from '@/router';
import { setupStore } from '@/store';
export default {
install(app: App<Element>) {
// 设置路由
setupRouter(app);
// 设置状态管理
setupStore(app);
// 设置代码高亮
setupHighLight(app);
},
};

View File

@ -1,26 +0,0 @@
import type { App } from 'vue';
import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router';
import error from '@/router/modules/error';
import home from '@/router/modules/home';
import remaining from '@/router/modules/remaining';
// 静态路由
const routes: RouteRecordRaw[] = [...remaining, ...home, ...error] as RouteRecordRaw[];
const router = createRouter({
history: createWebHashHistory(),
routes,
scrollBehavior: () => ({ left: 0, top: 0, behavior: 'smooth' }),
});
/** 全局注册 router */
export const setupRouter = (app: App<Element>) => {
app.use(router);
};
/** 重置路由 */
export const resetRouter = () => {
router.replace({ path: '/' }).then();
};
export default router;

View File

@ -1,13 +0,0 @@
import type { RouteRecordRaw } from 'vue-router';
import type { RouteConfigsTable } from '@/types/router/Route';
const routes: RouteRecordRaw[] | RouteConfigsTable[] = [
{
path: '/error',
component: () => import('@/views/error-page/404.vue'),
meta: { hidden: true },
},
];
export default routes;

View File

@ -1,24 +0,0 @@
import type { RouteRecordRaw } from 'vue-router';
import Layout from '@/layout/index.vue';
import type { RouteConfigsTable } from '@/types/router/Route';
const routes: RouteRecordRaw[] | RouteConfigsTable[] = [
{
path: '/',
name: '/',
component: Layout,
redirect: '/home',
meta: { transition: 'fade' },
children: [
{ path: '/home', name: 'home', component: () => import('@/views/home/index.vue') },
{
path: '/generator-code',
name: 'generatorCode',
component: () => import('@/views/generator-code/index.vue'),
},
],
},
];
export default routes;

View File

@ -1,20 +0,0 @@
import type { RouteRecordRaw } from 'vue-router';
import Layout from '@/layout/index.vue';
import type { RouteConfigsTable } from '@/types/router/Route';
const routes: RouteRecordRaw[] | RouteConfigsTable[] = [
{
path: '/redirect',
component: Layout,
meta: { hidden: true },
children: [
{
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect/index.vue'),
},
],
},
];
export default routes;

View File

@ -1,27 +0,0 @@
import { defineStore } from 'pinia';
import { isCSSColor, isPath } from '@/utils/regexp/regexpBackground';
const useAppStore = defineStore('appStore', {
state() {
return {
background: '',
};
},
getters: {},
actions: {
setBackground(background: string) {
if (isCSSColor(background)) {
this.background = background;
} else if (isPath(background)) {
const href = new URL(background, import.meta.url).href;
this.background = `url(${href})`;
} else {
const href = new URL('@/assets/images/common/bg/bg-layout.png', import.meta.url).href;
this.background = `url(${href})`;
}
},
},
});
export { useAppStore };

View File

@ -1,11 +0,0 @@
import { createPinia } from 'pinia';
import piniaPluginPersistedState from 'pinia-plugin-persistedstate';
import type { App } from 'vue';
const pinia = createPinia();
// 全局注册 store
export function setupStore(app: App<Element>) {
pinia.use(piniaPluginPersistedState);
app.use(pinia);
}

View File

@ -1,57 +0,0 @@
import { defineStore } from 'pinia';
import {
getDatabaseInfoMetaData,
getDatabaseTableList,
getTableColumnInfo,
getTableMetaData,
} from '@/api/table';
export const useTableStore = defineStore('tableStore', {
state: () => ({
databaseInfoMeta: undefined,
// 数据库所有的表
tableList: [],
tableListLoading: false,
}),
getters: {},
actions: {
/* 当前数据库信息 */
async loadDatabaseInfoMeta() {
const result = await getDatabaseInfoMetaData();
if (result.code === 200) {
this.databaseInfoMeta = result.data;
}
},
/* 数据库所有的表 */
async loadDatabaseTableList() {
this.tableListLoading = true;
const dbName = this.databaseInfoMeta?.currentDatabase;
const result = await getDatabaseTableList({ dbName });
this.tableList = result.data;
this.tableListLoading = false;
},
/* 获取表属性 */
async getTableMetaData(tableName: string) {
const result = await getTableMetaData({ tableName });
if (result.code !== 200) {
return {};
}
return result.data;
},
/* 获取表属性 */
async getTableColumnInfo(tableName: string) {
const result = await getTableColumnInfo({ tableName });
if (result.code !== 200) {
return {};
}
return result.data;
},
},
});

View File

@ -1,62 +0,0 @@
import { defineStore } from 'pinia';
import { generator, getVmsResourcePathList } from '@/api/vms';
export const useVmsStore = defineStore('vmsStore', {
// 开启持久化
// persist: true,
// persist: {
// paths: ['formValue', 'formOption'],
// },
state: () => ({
generators: [],
// 生成服务端内容
serverOptions: [],
// 生成前端内容
webOptions: [],
// 查询的表单
formValue: {
author: 'Bunny',
packageName: 'cn.bunny.services',
requestMapping: '/api',
className: '',
tableName: '',
simpleDateFormat: 'yyyy-MM-dd HH:mm:ss',
tablePrefixes: 't_,sys_,qrtz_,log_',
comment: '',
path: [],
},
// 表单选择内容
formOption: {
generatorServer: [],
generatorWeb: [],
},
}),
getters: {},
actions: {
/* 获取所有数据表 */
async generator(data: any) {
const result = await generator(data);
// 需要确保已经在 setup 中执行了 window.$message = message
this.generators = result.data.map((i: any) => ({ ...i, path: i.path.replace('.vm', '') }));
(window as any).$message.success(`生成成功,共 ${this.generators.length} 数据`);
},
/* 获取vms文件路径 */
async getVmsResourcePathList() {
const result = await getVmsResourcePathList();
// 需要确保已经在 setup 中执行了 window.$message = message
if (result.code !== 200) {
return {};
}
this.webOptions = result.data.web;
this.serverOptions = result.data.server;
},
},
});

View File

@ -1,16 +0,0 @@
declare global {
/* 环境便配置 */
declare interface ViteEnv {
VITE_APP_TITLE: string;
VITE_PORT: number;
VITE_PUBLIC_PATH: string;
VITE_APP_URL: string;
VITE_APP_BASE_API: string;
VITE_STRICT_PORT: boolean;
VITE_POST_CSS_PX_TO_VIEWPORT8_PLUGIN: boolean;
VITE_MOCK_DEV_SERVER: boolean;
VITE_MOCK_BASE_API: string;
VITE_CDN: boolean;
VITE_COMPRESSION: string;
}
}

View File

@ -1 +0,0 @@
type Recordable<T = any> = Record<string, T>;

View File

@ -1,6 +0,0 @@
// 基础后端返回内容
export interface BaseResult<T> {
code: number;
data: T;
message: string;
}

View File

@ -1,27 +0,0 @@
import type { RouteComponent } from 'vue-router';
/**
* @description `meta`
*/
interface CustomizeRouteMeta {
title: string;
subtitle: string;
transition: string;
}
/**
* @description
*/
export interface RouteConfigsTable {
/** 路由地址 `必填` */
path: string;
/** 路由名字(保持唯一)`可选` */
name?: string;
/** `Layout`组件 `可选` */
component?: RouteComponent;
/** 路由重定向 `可选` */
redirect?: string;
meta?: CustomizeRouteMeta;
/** 子路由配置项 */
children?: Array<RouteConfigsTable>;
}

View File

@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@ -1,24 +0,0 @@
/**
*
* @param text
*/
export const copy = (text: string) => {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
document.body.appendChild(textarea);
textarea.select();
try {
const success = document.execCommand('copy');
if (success) {
(window as any as any).$message.success('复制成功!');
} else {
(window as any).$message.success('复制失败!');
}
} catch (err: any) {
(window as any).$message.success('复制失败!请手动复制');
} finally {
document.body.removeChild(textarea);
}
};

View File

@ -1,45 +0,0 @@
export function downloadTextAsFile(text: string, filename: string) {
// 直接创建 File 对象(比 Blob 更高级)
const file = new File([text], filename, { type: 'text/plain' });
// 创建下载链接
const url = URL.createObjectURL(file);
const a = document.createElement('a');
a.href = url;
a.download = filename;
// 触发下载
document.body.appendChild(a);
a.click();
// 清理
requestIdleCallback(() => {
document.body.removeChild(a);
URL.revokeObjectURL(a.href);
});
}
export const downloadBlob = (response: any) => {
try {
// 从响应头获取文件名
const contentDisposition = response.headers['content-disposition'];
let fileName = 'download.zip';
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename="?(.+)"/);
if (fileNameMatch && fileNameMatch[1]) {
fileName = fileNameMatch[1];
}
}
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
} catch (error) {
console.error(error);
}
};

View File

@ -1,19 +0,0 @@
import 'nprogress/nprogress.css';
import NProgress from 'nprogress';
// 进度条
NProgress.configure({
// 动画方式
easing: 'ease',
// 递增进度条的速度
speed: 500,
// 是否显示加载ico
showSpinner: false,
// 自动递增间隔
trickleSpeed: 200,
// 初始化时的最小百分比
minimum: 0.3,
});
export default NProgress;

View File

@ -1,29 +0,0 @@
/** 判断是否是CSS颜色 */
function isCSSColor(str: string) {
// 匹配十六进制颜色(如 #fff, #ffffff
const hexColor = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
// 匹配RGB/RGBA颜色如 rgb(255, 255, 255), rgba(255, 255, 255, 0.5)
const rgbColor = /^rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(,\s*[\d\.]+)?\s*\)$/;
// 匹配HSL/HSLA颜色如 hsl(120, 100%, 50%), hsla(120, 100%, 50%, 0.5)
const hslColor = /^hsla?\(\s*\d{1,3}\s*,\s*\d{1,3}%\s*,\s*\d{1,3}%\s*(,\s*[\d\.]+)?\s*\)$/;
// 匹配预定义颜色名称(如 red, blue, green
const namedColor = /^[a-zA-Z]+$/;
return hexColor.test(str) || rgbColor.test(str) || hslColor.test(str) || namedColor.test(str);
}
/** 判断是否是相对路径或绝对路径 */
function isPath(str: string) {
// 匹配相对路径(如 ./path, ../path, path/to/file
const relativePath = /^\.{0,2}\/[^\/].*$/;
// 匹配绝对路径(如 /path/to/file, C:\path\to\file
const absolutePath = /^(?:\/|[A-Za-z]:\\).*$/;
return relativePath.test(str) || absolutePath.test(str);
}
export { isCSSColor, isPath };

View File

@ -1,13 +0,0 @@
<template>&nbsp;</template>
<script>
import { useMessage } from 'naive-ui';
import { defineComponent } from 'vue';
// naive ui https://www.naiveui.com/zh-CN/light/components/message
export default defineComponent({
setup() {
window.$message = useMessage();
},
});
</script>

View File

@ -1,237 +0,0 @@
<script lang="ts" setup>
import { useRouter } from 'vue-router';
const router = useRouter();
</script>
<template>
<div class="page-container">
<div class="pic-404">
<img alt="404" class="pic-404__parent" src="../../assets/images/error/404.png" />
<img alt="404" class="pic-404__child left" src="../../assets/images/error/404_cloud.png" />
<img alt="404" class="pic-404__child mid" src="../../assets/images/error/404_cloud.png" />
<img alt="404" class="pic-404__child right" src="../../assets/images/error/404_cloud.png" />
</div>
<div class="bullshit">
<div class="bullshit__oops">OOPS!</div>
<div class="bullshit__info">
All rights reserved
<a href="https://wallstreetcn.com" style="color: #20a0ff" target="_blank">wallstreetcn</a>
</div>
<div class="bullshit__headline">The webmaster said that you can not enter this page...</div>
<div class="bullshit__info">
Please check that the URL you entered is correct, or click the button below to return to the
homepage.
</div>
<a class="bullshit__return-home" href="/" @click.prevent="router.replace('/')">
Back to home
</a>
</div>
</div>
</template>
<style lang="scss" scoped>
.page-container {
display: flex;
padding: 100px;
.pic-404 {
width: 600px;
overflow: hidden;
&__parent {
width: 100%;
}
&__child {
&.left {
top: 17px;
left: 220px;
width: 80px;
opacity: 0;
animation-name: cloudLeft;
animation-duration: 2s;
animation-timing-function: linear;
animation-delay: 1s;
animation-fill-mode: forwards;
}
&.mid {
top: 10px;
left: 420px;
width: 46px;
opacity: 0;
animation-name: cloudMid;
animation-duration: 2s;
animation-timing-function: linear;
animation-delay: 1.2s;
animation-fill-mode: forwards;
}
&.right {
top: 100px;
left: 500px;
width: 62px;
opacity: 0;
animation-name: cloudRight;
animation-duration: 2s;
animation-timing-function: linear;
animation-delay: 1s;
animation-fill-mode: forwards;
}
@keyframes cloudLeft {
0% {
top: 17px;
left: 220px;
opacity: 0;
}
20% {
top: 33px;
left: 188px;
opacity: 1;
}
80% {
top: 81px;
left: 92px;
opacity: 1;
}
100% {
top: 97px;
left: 60px;
opacity: 0;
}
}
@keyframes cloudMid {
0% {
top: 10px;
left: 420px;
opacity: 0;
}
20% {
top: 40px;
left: 360px;
opacity: 1;
}
70% {
top: 130px;
left: 180px;
opacity: 1;
}
100% {
top: 160px;
left: 120px;
opacity: 0;
}
}
@keyframes cloudRight {
0% {
top: 100px;
left: 500px;
opacity: 0;
}
20% {
top: 120px;
left: 460px;
opacity: 1;
}
80% {
top: 180px;
left: 340px;
opacity: 1;
}
100% {
top: 200px;
left: 300px;
opacity: 0;
}
}
}
}
.bullshit {
width: 300px;
padding: 30px 0;
overflow: hidden;
&__oops {
margin-bottom: 20px;
font-size: 32px;
font-weight: bold;
line-height: 40px;
color: #1482f0;
opacity: 0;
animation-name: slideUp;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
&__headline {
margin-bottom: 10px;
font-size: 20px;
font-weight: bold;
line-height: 24px;
color: #222;
opacity: 0;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.1s;
animation-fill-mode: forwards;
}
&__info {
margin-bottom: 30px;
font-size: 13px;
line-height: 21px;
color: grey;
opacity: 0;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.2s;
animation-fill-mode: forwards;
}
&__return-home {
display: block;
float: left;
width: 110px;
height: 36px;
font-size: 14px;
line-height: 36px;
color: #fff;
text-align: center;
cursor: pointer;
background: #1482f0;
border-radius: 100px;
opacity: 0;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.3s;
animation-fill-mode: forwards;
}
@keyframes slideUp {
0% {
opacity: 0;
transform: translateY(60px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
}
}
</style>

View File

@ -1,68 +0,0 @@
import { NTag } from 'naive-ui';
import type { JSX } from 'vue/jsx-runtime';
export const columns: any = [
{
title: '序号',
key: 'no',
titleAlign: 'center',
align: 'center',
render(row: any, index: number) {
return index + 1;
},
},
{
title: '列名称',
key: 'columnName',
titleAlign: 'center',
align: 'center',
render(row: any): JSX.Element {
return <NTag type="primary">{row.columnName}</NTag>;
},
},
{
title: '字段名称',
key: 'lowercaseName',
titleAlign: 'center',
align: 'center',
render(row: any): JSX.Element {
return <NTag>{row.lowercaseName}</NTag>;
},
},
{
title: '数据库字段类型',
key: 'jdbcType',
titleAlign: 'center',
align: 'center',
render(row: any): JSX.Element {
return <NTag>{row.jdbcType}</NTag>;
},
},
{
title: 'Java类型',
key: 'javaType',
titleAlign: 'center',
align: 'center',
render(row: any): JSX.Element {
return <NTag type="warning">{row.javaType}</NTag>;
},
},
{
title: '是否为主键',
key: 'isPrimaryKey',
titleAlign: 'center',
align: 'center',
render(row: any): JSX.Element {
return row.isPrimaryKey ? <NTag type="error"></NTag> : <NTag type="success"></NTag>;
},
},
{
title: '字段注释',
key: 'comment',
titleAlign: 'center',
align: 'center',
render(row: any): JSX.Element {
return <NTag type="info">{row.comment}</NTag>;
},
},
];

View File

@ -1,35 +0,0 @@
<script lang="ts" setup>
import { NDataTable } from 'naive-ui';
import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useTableStore } from '@/store/modules/table';
import { columns } from '@/views/generator-code/column-field/columns';
const route = useRoute();
const tableStore = useTableStore();
//
const datalist = ref([]);
const loading = ref(false);
/* 数据库列信息 */
const getColumnInfo = async () => {
loading.value = true;
const tableName: any = route.query.tableName;
datalist.value = await tableStore.getTableColumnInfo(tableName);
loading.value = false;
};
onMounted(() => {
getColumnInfo();
});
</script>
<template>
<!-- 当前表的列字段 -->
<n-data-table :bordered="true" :columns="columns" :data="datalist" :loading="loading" />
</template>

View File

@ -1,54 +0,0 @@
<script lang="ts" setup>
import { NGradientText } from 'naive-ui';
import { storeToRefs } from 'pinia';
import { onMounted } from 'vue';
import { useTableStore } from '@/store/modules/table';
const tableStore = useTableStore();
const { databaseInfoMeta } = storeToRefs(tableStore);
onMounted(() => {
tableStore.loadDatabaseInfoMeta();
});
</script>
<template>
<div class="database-info">
<p>
数据库产品名称
<n-gradient-text type="primary">{{ databaseInfoMeta?.databaseProductName }}</n-gradient-text>
</p>
<p>
数据库产品版本
<n-gradient-text type="primary">
{{ databaseInfoMeta?.databaseProductVersion }}
</n-gradient-text>
</p>
<p>
驱动名称
<n-gradient-text type="primary">{{ databaseInfoMeta?.driverName }}</n-gradient-text>
</p>
<p>
数据库驱动版本
<n-gradient-text type="primary">{{ databaseInfoMeta?.driverVersion }}</n-gradient-text>
</p>
<p>
数据库用户
<n-gradient-text type="primary">{{ databaseInfoMeta?.username }}</n-gradient-text>
</p>
</div>
</template>
<style lang="scss" scoped>
.database-info {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
flex-direction: column;
p {
margin: 5px 0 0 0;
}
}
</style>

View File

@ -1,255 +0,0 @@
<script lang="ts" setup>
import {
NButton,
NButtonGroup,
NCheckbox,
NCheckboxGroup,
NForm,
NFormItemGi,
NGrid,
NGridItem,
NInput,
NSpace,
useMessage,
} from 'naive-ui';
import { storeToRefs } from 'pinia';
import { onMounted, ref } from 'vue';
import { computed } from 'vue-demi';
import { useRoute } from 'vue-router';
import { fetchTableInfo } from '@/api/sqlParser';
import { downloadByZip } from '@/api/vms';
import SelectButtonGroup from '@/components/select-button-group.vue';
import { useVmsStore } from '@/store/modules/vms';
import { downloadBlob, downloadTextAsFile } from '@/utils/file';
import {
formValueInit,
selectAll,
selectAllInvert,
selectCancelAll,
validateFormValue,
} from '@/views/generator-code/generator/hook';
import { rules } from '@/views/generator-code/generator/option';
const route = useRoute();
const vmsStore = useVmsStore();
const { formValue, formOption } = storeToRefs(vmsStore);
const message = useMessage();
const formRef = ref();
const hasDownloadZip = computed(
() => !(formOption.value.generatorWeb.length > 0 || formOption.value.generatorServer.length > 0)
);
// Sql
const sql = ref();
/* 提交表单 */
const onSubmit = (e: MouseEvent) => {
e.preventDefault();
formRef.value?.validate(async (errors: any) => {
if (!errors) {
validateFormValue();
//
await vmsStore.generator(formValue.value);
} else {
errors.forEach((error: any) => {
error.forEach((err: any) => {
message.error(`${err.message}->${err.field}`);
});
});
}
});
};
/* 清空已经生成的代码 */
const clearGeneratorCode = () => {
vmsStore.generators = [];
};
/* 下载全部 */
const downloadAll = () => {
vmsStore.generators.forEach((vms) => {
const code = vms.code;
const path = vms.path.split('/')[1];
downloadTextAsFile(code, path);
});
};
/* 下载zip文件 */
const downloadZipFile = async () => {
validateFormValue();
const result = await downloadByZip(formValue.value);
downloadBlob(result);
};
/* 解析 SQL 语句信息 */
const sqlParser = async () => {
if (!sql.value) {
message.warning('SQL 为空');
return;
}
const params = { sql: sql.value };
const { data } = await fetchTableInfo(params);
// sql
formValue.value.comment = data.comment;
formValue.value.tableName = data.tableName;
formValueInit(data.tableName);
};
onMounted(() => {
//
const tableName: any = route.query.tableName;
formValueInit(tableName);
vmsStore.getVmsResourcePathList();
});
</script>
<template>
<n-form ref="formRef" :label-width="80" :model="formValue" :rules="rules">
<n-grid cols="24" item-responsive responsive="screen">
<n-form-item-gi
label="如果有sql会生成sql中的信息点击【解析SQL】会替换【表名称】和【注释名称】"
path="sql"
span="24"
>
<div class="flex flex-wrap flex-col w-full">
<n-input
v-model:value="sql"
:autosize="{ minRows: 3 }"
class="w-full"
placeholder="SQL语句"
type="textarea"
/>
<n-button-group class="mt-2">
<n-button type="primary" @click="sqlParser">解析SQL</n-button>
<n-button type="error" @click="sql = null">清空输入框</n-button>
</n-button-group>
</div>
</n-form-item-gi>
</n-grid>
<!-- 需要提交的生成表单 -->
<n-grid :cols="24" :x-gap="5" item-responsive responsive="screen">
<n-form-item-gi label="作者名称" path="author" span="12 m:8 l:6">
<n-input v-model:value="formValue.author" placeholder="作者名称" />
</n-form-item-gi>
<n-form-item-gi label="requestMapping名称" path="requestMapping" span="12 m:8 l:6">
<n-input v-model:value="formValue.requestMapping" placeholder="requestMapping名称" />
</n-form-item-gi>
<n-form-item-gi label="表名称" path="tableName" span="24 m:8 l:6">
<n-input v-model:value="formValue.tableName" placeholder="表名称" />
</n-form-item-gi>
<n-form-item-gi label="类名称" path="className" span="24 m:8 l:6">
<n-input v-model:value="formValue.className" placeholder="类名称" />
</n-form-item-gi>
<n-form-item-gi label="包名称" path="packageName" span="24 m:8 l:6">
<n-input v-model:value="formValue.packageName" placeholder="包名称" />
</n-form-item-gi>
<n-form-item-gi label="时间格式" path="simpleDateFormat" span="24 m:8 l:6">
<n-input v-model:value="formValue.simpleDateFormat" placeholder="时间格式" />
</n-form-item-gi>
<n-form-item-gi label="去除开头前缀" path="tablePrefixes" span="12 m:8 l:6">
<n-input v-model:value="formValue.tablePrefixes" placeholder="去除开头前缀" />
</n-form-item-gi>
<n-form-item-gi label="注释名称" path="comment" span="12 m:8 l:6">
<n-input v-model:value="formValue.comment" placeholder="修改注释名称" />
</n-form-item-gi>
</n-grid>
<!-- 需要生成的模板 -->
<n-grid :cols="24" :x-gap="5" item-responsive responsive="screen">
<n-form-item-gi label="生成后端" path="generatorServer" span="24 m:24 l:12">
<n-checkbox-group v-model:value="formOption.generatorServer">
<n-space>
<n-checkbox
v-for="(item, index) in vmsStore.serverOptions"
:key="index"
:value="item.name"
>
{{ item.label }}
</n-checkbox>
<!-- 选择按钮 -->
<select-button-group
v-model:selected="formOption.generatorServer"
:data="vmsStore.serverOptions"
id-key="name"
/>
</n-space>
</n-checkbox-group>
</n-form-item-gi>
<n-form-item-gi label="生成前端" path="generatorWeb" span="24 m:24 l:12">
<n-checkbox-group v-model:value="formOption.generatorWeb">
<n-space>
<n-checkbox
v-for="(item, index) in vmsStore.webOptions"
:key="index"
v-model:value="item.name"
>
{{ item.label }}
</n-checkbox>
<!-- 选择按钮 -->
<select-button-group
v-model:selected="formOption.generatorWeb"
:data="vmsStore.webOptions"
id-key="name"
/>
</n-space>
</n-checkbox-group>
</n-form-item-gi>
</n-grid>
<!-- 操作选项按钮 -->
<n-grid cols="24" item-responsive responsive="screen">
<n-grid-item class="mt-2" span="24 m:12 l:8">
<n-button attr-type="button" type="success" @click="selectAll">全部选择</n-button>
<n-button attr-type="button" type="warning" @click="selectAllInvert">全部反选</n-button>
<n-button attr-type="button" type="error" @click="selectCancelAll">全选取消</n-button>
</n-grid-item>
<n-grid-item class="mt-2" span="24 m:12 l:8">
<n-button attr-type="button" type="success" @click="onSubmit">开始生成</n-button>
<n-button attr-type="button" type="error" @click="clearGeneratorCode">清空已生成</n-button>
<n-button
:disabled="!(vmsStore.generators.length > 0)"
attr-type="button"
type="primary"
@click="downloadAll"
>
下载全部 {{ vmsStore.generators.length }}
</n-button>
</n-grid-item>
<n-grid-item class="mt-2" span="24 m:12 l:8">
<n-button
:disabled="hasDownloadZip"
attr-type="button"
class="w-full"
type="success"
@click="downloadZipFile"
>
下载zip
</n-button>
</n-grid-item>
</n-grid>
</n-form>
</template>

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