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:
commit
d9cced0eba
|
@ -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
443
README.md
|
@ -1,53 +1,402 @@
|
|||
# 代码生成器
|
||||
# 🚀 Bunny Code Generator 代码生成器系统文档
|
||||
|
||||
## 功能展示
|
||||
[](LICENSE)[]()[]()
|
||||
|
||||
点击 `表名` 或 `注释内容` 跳转到另一个页面
|
||||
## 1. 系统架构 🏗️
|
||||
|
||||

|
||||
### 1.1 架构图
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 内置字段
|
||||
|
||||
```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]
|
||||
```
|
||||
|
||||

|
||||
### 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. 支持项目☕
|
||||
|
||||
如果这个项目对您有帮助,可以考虑支持我们:
|
||||
|
||||

|
||||
|
||||
**Happy Coding!** 🎉
|
|
@ -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>
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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"
|
|
@ -1,10 +0,0 @@
|
|||
_ _
|
||||
| |__ _ _ _ __ _ __ _ _ (_) __ ___ ____ _
|
||||
| '_ \| | | | '_ \| '_ \| | | | | |/ _` \ \ / / _` |
|
||||
| |_) | |_| | | | | | | | |_| | | | (_| |\ V | (_| |
|
||||
|_.__/ \__,_|_| |_|_| |_|\__, | _/ |\__,_| \_/ \__,_|
|
||||
|___/ |__/
|
||||
|
||||
Service Name${spring.application.name}
|
||||
SpringBoot Version: ${spring-boot.version}${spring-boot.formatted-version}
|
||||
SpringActive:${spring.profiles.active}
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,11 +0,0 @@
|
|||
dist
|
||||
node_modules
|
||||
public
|
||||
.husky
|
||||
.vscode
|
||||
.idea
|
||||
*.sh
|
||||
*.md
|
||||
|
||||
src/assets
|
||||
stats.html
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
dist
|
||||
node_modules
|
||||
public
|
||||
.husky
|
||||
.vscode
|
||||
.idea
|
||||
*.sh
|
||||
*.md
|
||||
|
||||
src/assets
|
||||
stats.html
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"hash": "727e0168",
|
||||
"configHash": "10198a12",
|
||||
"lockfileHash": "530f8857",
|
||||
"browserHash": "41b39cb9",
|
||||
"optimized": {},
|
||||
"chunks": {}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"type": "module"
|
||||
}
|
|
@ -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.log,terser打包慢,但能去除 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`;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
|
@ -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)
|
||||
* 平台采用国内cdn:https://www.bootcdn.cn,当然你也可以选择 https://unpkg.com 或者 https://www.jsdelivr.com
|
||||
* 注意:上面提到的仅限外网使用也不是完全肯定的,如果你们公司内网部署的有相关js、css文件,也可以将下面配置对应改一下,整一套内网版cdn
|
||||
*/
|
||||
export const cdn = importToCDN({
|
||||
//(prodUrl解释: name: 对应下面modules的name,version: 自动读取本地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;
|
||||
};
|
|
@ -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 *;`,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
|
@ -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__),
|
||||
};
|
||||
};
|
|
@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
|
@ -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 };
|
|
@ -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),
|
||||
];
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
import { pathResolve } from './utils';
|
||||
|
||||
export const resolve = () => {
|
||||
return {
|
||||
alias: {
|
||||
'@': pathResolve('../src'),
|
||||
},
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
};
|
|
@ -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;"]
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
|
@ -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>
|
|
@ -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 |
|
@ -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>
|
|
@ -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;
|
|
@ -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;
|
|
@ -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,
|
||||
});
|
||||
};
|
|
@ -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 });
|
||||
};
|
|
@ -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 |
|
@ -1,2 +0,0 @@
|
|||
@use "src/rotate";
|
||||
@use "src/transition";
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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%);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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%;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* 令牌缓存Key
|
||||
*/
|
||||
export const TOKEN_KEY = 'accessToken';
|
|
@ -1,9 +0,0 @@
|
|||
/** 响应码枚举 */
|
||||
export const enum ResultEnum {
|
||||
// 成功
|
||||
SUCCESS = '200',
|
||||
// 错误
|
||||
ERROR = 'B0001',
|
||||
// 令牌无效或过期
|
||||
TOKEN_INVALID = '209',
|
||||
}
|
|
@ -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>© 2025 Bunny.保留所有权利.</p>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
|
@ -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');
|
|
@ -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);
|
||||
};
|
|
@ -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);
|
||||
},
|
||||
};
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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 };
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
},
|
||||
},
|
||||
});
|
|
@ -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;
|
||||
},
|
||||
},
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
type Recordable<T = any> = Record<string, T>;
|
|
@ -1,6 +0,0 @@
|
|||
// 基础后端返回内容
|
||||
export interface BaseResult<T> {
|
||||
code: number;
|
||||
data: T;
|
||||
message: string;
|
||||
}
|
|
@ -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>;
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
/// <reference types="vite/client" />
|
|
@ -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);
|
||||
}
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
};
|
|
@ -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;
|
|
@ -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 };
|
|
@ -1,13 +0,0 @@
|
|||
<template> </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>
|
|
@ -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>
|
|
@ -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>;
|
||||
},
|
||||
},
|
||||
];
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
Loading…
Reference in New Issue