mirror of
https://github.com/KwiTsukasa/kt-template-online-api.git
synced 2026-05-27 15:44:54 +08:00
first commit
This commit is contained in:
commit
73c4429a52
11
.env
Normal file
11
.env
Normal file
@ -0,0 +1,11 @@
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=qwqvqaqeq2333KT
|
||||
DB_DATABASE=shy_template
|
||||
DB_SYNC=true
|
||||
|
||||
MINIO_ENDPOINT=192.168.1.206
|
||||
MINIO_PORT=9000
|
||||
MINIO_ACCESS_KEY=minioadmin
|
||||
MINIO_SECRET_KEY=minioadmin
|
||||
11
.env.prod
Normal file
11
.env.prod
Normal file
@ -0,0 +1,11 @@
|
||||
DB_HOST=192.168.1.206
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=3h1@admin
|
||||
DB_DATABASE=shy_template
|
||||
DB_SYNC=false
|
||||
|
||||
MINIO_ENDPOINT=192.168.1.206
|
||||
MINIO_PORT=9000
|
||||
MINIO_ACCESS_KEY=minioadmin
|
||||
MINIO_SECRET_KEY=minioadmin
|
||||
26
.eslintrc.js
Normal file
26
.eslintrc.js
Normal file
@ -0,0 +1,26 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
tsconfigRootDir: __dirname,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin',"typeorm"],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
ignorePatterns: ['.eslintrc.js'],
|
||||
rules: {
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'prettier/prettier': 'off',
|
||||
},
|
||||
};
|
||||
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
# compiled output
|
||||
/dist
|
||||
/node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
pnpm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Tests
|
||||
/coverage
|
||||
/.nyc_output
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# env
|
||||
/.env.dev
|
||||
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
35
README.md
Normal file
35
README.md
Normal file
@ -0,0 +1,35 @@
|
||||
## 技术总览
|
||||
|
||||
`Node`、`Ts`、`Nest.js`、`TypeORM`、`MySQL`、`Express`
|
||||
|
||||
## 项目简介
|
||||
|
||||
此项目为`kt-template-online`服务端,基于`Node`、`Ts`开发
|
||||
|
||||
使用服务端框架`Nest.js`构建项目以及`ORM`框架`TypeORM`快速生成`SQL`语句以及映射`SQL`库表字段关系
|
||||
|
||||
## 运行项目
|
||||
|
||||
```bash
|
||||
# 运行
|
||||
$ pnpm start
|
||||
|
||||
# 开发环境
|
||||
$ pnpm start:dev
|
||||
|
||||
# 生产环境
|
||||
$ pnpm start:prod
|
||||
```
|
||||
|
||||
## 测试
|
||||
|
||||
```bash
|
||||
# unit tests
|
||||
$ pnpm test
|
||||
|
||||
# e2e tests
|
||||
$ pnpm test:e2e
|
||||
|
||||
# test coverage
|
||||
$ pnpm test:cov
|
||||
```
|
||||
19
dockerfile
Normal file
19
dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
# 引用基础镜像
|
||||
FROM node:22.14.0-release
|
||||
|
||||
# 指定工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 拷贝文件
|
||||
COPY . .
|
||||
|
||||
# 安装依赖
|
||||
RUN npm install
|
||||
|
||||
RUN npm install pm2 -g
|
||||
|
||||
# # 声明暴露端口号
|
||||
EXPOSE 48085
|
||||
|
||||
CMD npm run start:prod
|
||||
|
||||
14
ecosystem.config.js
Normal file
14
ecosystem.config.js
Normal file
@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'shy-template-server',
|
||||
script: './dist/main.js',
|
||||
env_prod: {
|
||||
NODE_ENV: 'prod',
|
||||
},
|
||||
env_dev: {
|
||||
NODE_ENV: 'dev',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
8
nest-cli.json
Normal file
8
nest-cli.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
}
|
||||
}
|
||||
98
package.json
Normal file
98
package.json
Normal file
@ -0,0 +1,98 @@
|
||||
{
|
||||
"name": "kt-template-online-api",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "nest build && NODE_ENV=prod node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^9.4.3",
|
||||
"@nestjs/config": "^2.3.4",
|
||||
"@nestjs/core": "^9.4.3",
|
||||
"@nestjs/jwt": "^10.2.0",
|
||||
"@nestjs/mapped-types": "^2.1.1",
|
||||
"@nestjs/passport": "^9.0.3",
|
||||
"@nestjs/platform-express": "^9.4.3",
|
||||
"@nestjs/swagger": "^7.4.2",
|
||||
"@nestjs/typeorm": "^9.0.1",
|
||||
"@types/dotenv": "^8.2.3",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/multer": "^1.4.13",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^16.6.1",
|
||||
"express-session": "^1.19.0",
|
||||
"lodash": "^4.18.1",
|
||||
"minio": "^8.0.7",
|
||||
"mssql": "^9.3.2",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"mysql2": "^3.22.3",
|
||||
"nestjs-knife4j-plus": "^1.0.7",
|
||||
"nestjs-minio-client": "^2.2.0",
|
||||
"passport": "^0.6.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"reflect-metadata": "^0.1.14",
|
||||
"rxjs": "^7.8.2",
|
||||
"svg-captcha": "^1.4.0",
|
||||
"typeorm": "^0.3.28"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^9.5.0",
|
||||
"@nestjs/schematics": "^9.2.0",
|
||||
"@nestjs/testing": "^9.4.3",
|
||||
"@types/express": "^4.17.25",
|
||||
"@types/express-session": "^1.19.0",
|
||||
"@types/jest": "29.2.4",
|
||||
"@types/node": "18.11.18",
|
||||
"@types/passport-jwt": "^3.0.13",
|
||||
"@types/passport-local": "^1.0.38",
|
||||
"@types/supertest": "^2.0.16",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^8.10.2",
|
||||
"eslint-plugin-prettier": "^4.2.5",
|
||||
"eslint-plugin-typeorm": "0.0.19",
|
||||
"jest": "29.3.1",
|
||||
"prettier": "^2.8.8",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^6.3.4",
|
||||
"ts-jest": "29.0.3",
|
||||
"ts-loader": "^9.5.7",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "4.1.1",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
8241
pnpm-lock.yaml
Normal file
8241
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
13
src/app.controller.ts
Normal file
13
src/app.controller.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Controller, Get, Redirect } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get()
|
||||
@Redirect('/api#/', 301)
|
||||
getHome() {
|
||||
return { url: '/api#/' };
|
||||
}
|
||||
}
|
||||
57
src/app.module.ts
Normal file
57
src/app.module.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { AppService } from './app.service';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { MinioModule } from 'nestjs-minio-client';
|
||||
import { ComponentModule } from './component/component.module';
|
||||
import { DictModule } from './dict/dict.module';
|
||||
import { SaveMiddleware } from './middleware/save.middleware';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
envFilePath: `.env${
|
||||
process.env.NODE_ENV ? `.${process.env.NODE_ENV}` : ''
|
||||
}`,
|
||||
}),
|
||||
TypeOrmModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => {
|
||||
return {
|
||||
type: 'mysql',
|
||||
host: configService.get('DB_HOST'),
|
||||
port: configService.get('DB_PORT'),
|
||||
username: configService.get('DB_USERNAME'),
|
||||
password: configService.get('DB_PASSWORD'),
|
||||
database: configService.get('DB_DATABASE'),
|
||||
synchronize: configService.get('DB_SYNC'),
|
||||
entities: [__dirname + '/**/*.entity.js'],
|
||||
};
|
||||
},
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
MinioModule.registerAsync({
|
||||
isGlobal: true,
|
||||
imports: [ConfigModule],
|
||||
useFactory: (configService: ConfigService) => {
|
||||
return {
|
||||
endPoint: configService.get('MINIO_ENDPOINT'),
|
||||
port: parseInt(configService.get('MINIO_PORT')),
|
||||
useSSL: false,
|
||||
accessKey: configService.get('MINIO_ACCESS_KEY'),
|
||||
secretKey: configService.get('MINIO_SECRET_KEY'),
|
||||
};
|
||||
},
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
ComponentModule,
|
||||
DictModule,
|
||||
],
|
||||
providers: [AppService, ConfigService],
|
||||
})
|
||||
export class AppModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer.apply(SaveMiddleware).forRoutes('*/save');
|
||||
}
|
||||
}
|
||||
8
src/app.service.ts
Normal file
8
src/app.service.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return 'Hello World!';
|
||||
}
|
||||
}
|
||||
9
src/common/index.ts
Normal file
9
src/common/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export function DecodeDictKey(dict: Dict[]): PropertyDecorator {
|
||||
return (target, key: string | symbol) => {
|
||||
Reflect.defineProperty(target, `_${key.toString()}`, {
|
||||
set(newVal) {
|
||||
this[key] = dict.find((i) => i.value == newVal).label;
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
150
src/component/component.controller.ts
Normal file
150
src/component/component.controller.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Res,
|
||||
Query,
|
||||
Body,
|
||||
HttpStatus,
|
||||
HttpCode,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiExtraModels,
|
||||
ApiOkResponse,
|
||||
ApiOperation,
|
||||
ApiProperty,
|
||||
ApiQuery,
|
||||
ApiTags,
|
||||
PartialType,
|
||||
} from '@nestjs/swagger';
|
||||
import { ToolsService } from 'src/utils/tool.service';
|
||||
import { ComponentService } from './component.service';
|
||||
import { Component } from './component.entity';
|
||||
import { PaginatedDto } from '@/utils/constant';
|
||||
import { ComponentDto } from './component.dto';
|
||||
|
||||
class CompPageDto
|
||||
extends PartialType(Component)
|
||||
implements PageParams<Component>
|
||||
{
|
||||
@ApiProperty({
|
||||
type: Number,
|
||||
default: 1,
|
||||
})
|
||||
pageNo: number;
|
||||
@ApiProperty({
|
||||
type: Number,
|
||||
default: 10,
|
||||
})
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
class CompPageResDto extends PaginatedDto {
|
||||
@ApiProperty({
|
||||
type: [ComponentDto],
|
||||
})
|
||||
list: ComponentDto[];
|
||||
}
|
||||
|
||||
@Controller('component')
|
||||
@ApiTags('component')
|
||||
@ApiExtraModels(PaginatedDto, ComponentDto)
|
||||
export class ComponentController {
|
||||
constructor(
|
||||
private readonly toolsService: ToolsService,
|
||||
private readonly componentService: ComponentService,
|
||||
) {} //注入服务
|
||||
|
||||
@Get('allList')
|
||||
@ApiOperation({ summary: '获取组件列表' })
|
||||
@ApiOkResponse({ type: [ComponentDto] })
|
||||
async getAllList(@Res() res) {
|
||||
const list = await this.componentService.all();
|
||||
res.send(this.toolsService.res(HttpStatus.OK, '操作成功', list));
|
||||
}
|
||||
|
||||
@Get('list')
|
||||
@ApiOperation({ summary: '获取组件列表分页' })
|
||||
@ApiQuery({ type: [CompPageDto] })
|
||||
@ApiOkResponse({
|
||||
type: CompPageResDto,
|
||||
})
|
||||
async getList(
|
||||
@Res() res,
|
||||
@Query() { pageNo, pageSize, ...args }: PageParams<ComponentDto>,
|
||||
): Promise<CompPageResDto> {
|
||||
const list = await this.componentService.page({
|
||||
pageNo,
|
||||
pageSize,
|
||||
...args,
|
||||
});
|
||||
res.send(this.toolsService.res(HttpStatus.OK, '操作成功', list));
|
||||
return;
|
||||
}
|
||||
|
||||
@Post('save')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: '保存组件' })
|
||||
async save(@Res() res, @Body() component: Component) {
|
||||
const save = await this.componentService.save(component);
|
||||
|
||||
if (!save) {
|
||||
res.send(this.toolsService.res(HttpStatus.BAD_REQUEST, '操作失败', null));
|
||||
return;
|
||||
}
|
||||
|
||||
res.send(this.toolsService.res(HttpStatus.OK, '操作成功', save.id));
|
||||
return;
|
||||
}
|
||||
|
||||
@Post('remove')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: '删除组件' })
|
||||
@ApiQuery({ name: 'id', type: String })
|
||||
async remove(@Res() res, @Query('id') id) {
|
||||
const remove = await this.componentService.remove(id);
|
||||
|
||||
if (!remove) {
|
||||
res.send(
|
||||
this.toolsService.res(HttpStatus.BAD_REQUEST, '操作失败', remove),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
res.send(this.toolsService.res(HttpStatus.OK, '操作成功', remove));
|
||||
return;
|
||||
}
|
||||
|
||||
@Post('update')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: '编辑组件' })
|
||||
async update(@Res() res, @Body() component: Component) {
|
||||
const update = await this.componentService.update(component);
|
||||
|
||||
if (!update) {
|
||||
res.send(
|
||||
this.toolsService.res(HttpStatus.BAD_REQUEST, '操作失败', update),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
res.send(this.toolsService.res(HttpStatus.OK, '操作成功', update));
|
||||
return;
|
||||
}
|
||||
|
||||
@Get('detail')
|
||||
@ApiOperation({ summary: '组件详情' })
|
||||
@ApiQuery({ name: 'id', type: String })
|
||||
@ApiOkResponse({ type: ComponentDto })
|
||||
async detail(@Res() res, @Query('id') id) {
|
||||
const detail = await this.componentService.find(id);
|
||||
|
||||
if (!detail) {
|
||||
res.send(this.toolsService.res(HttpStatus.BAD_REQUEST, '操作失败', null));
|
||||
return;
|
||||
}
|
||||
|
||||
res.send(this.toolsService.res(HttpStatus.OK, '操作成功', detail));
|
||||
return;
|
||||
}
|
||||
}
|
||||
23
src/component/component.dto.ts
Normal file
23
src/component/component.dto.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { DecodeDictKey } from '@/common';
|
||||
import { Component } from './component.entity';
|
||||
import { DictKeyEnum, DictKeyMap } from '@/utils/constant';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class ComponentDto extends Component {
|
||||
[x: string]: any;
|
||||
@ApiProperty()
|
||||
@DecodeDictKey(DictKeyMap.get(DictKeyEnum.COMPONENT_TYPE))
|
||||
typeMsg: string;
|
||||
|
||||
@ApiProperty()
|
||||
@DecodeDictKey([
|
||||
...DictKeyMap.get(DictKeyEnum.CHART),
|
||||
...DictKeyMap.get(DictKeyEnum.COMPONENT),
|
||||
])
|
||||
componentTypeMsg: string;
|
||||
constructor(component: Component) {
|
||||
super(component);
|
||||
this._typeMsg = component.type;
|
||||
this._componentTypeMsg = component.componentType;
|
||||
}
|
||||
}
|
||||
77
src/component/component.entity.ts
Normal file
77
src/component/component.entity.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { ComponentTypeEnum, ComponentEnum } from '@/utils/constant';
|
||||
|
||||
|
||||
@Entity()
|
||||
export class Component {
|
||||
constructor(component?: Component) {
|
||||
Object.assign(this, component);
|
||||
}
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@ApiProperty()
|
||||
@Column({
|
||||
default: '',
|
||||
})
|
||||
name: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: 'enum',
|
||||
enum: ComponentTypeEnum,
|
||||
})
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: ComponentTypeEnum,
|
||||
})
|
||||
type: number;
|
||||
|
||||
@ApiProperty({
|
||||
type: 'enum',
|
||||
enum: ComponentEnum,
|
||||
})
|
||||
@Column({
|
||||
name: 'component_type',
|
||||
type: 'enum',
|
||||
enum: ComponentEnum,
|
||||
})
|
||||
componentType: number;
|
||||
|
||||
@ApiProperty()
|
||||
@Column({
|
||||
type: 'mediumtext',
|
||||
nullable: false,
|
||||
})
|
||||
image: string;
|
||||
|
||||
@ApiProperty()
|
||||
@Column({
|
||||
type: 'mediumtext',
|
||||
nullable: false,
|
||||
})
|
||||
template: string;
|
||||
|
||||
@CreateDateColumn({
|
||||
name: 'create_time',
|
||||
})
|
||||
createTime: Date;
|
||||
|
||||
@UpdateDateColumn({
|
||||
name: 'update_time',
|
||||
})
|
||||
updateTime: Date;
|
||||
|
||||
@Column({
|
||||
default: 0,
|
||||
})
|
||||
is_deleted: boolean;
|
||||
}
|
||||
14
src/component/component.module.ts
Normal file
14
src/component/component.module.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ComponentController } from './component.controller';
|
||||
import { ComponentService } from './component.service';
|
||||
import { Component } from './component.entity';
|
||||
import { ToolsService } from 'src/utils/tool.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Component])],
|
||||
controllers: [ComponentController],
|
||||
providers: [ComponentService, ToolsService],
|
||||
exports: [ComponentService],
|
||||
})
|
||||
export class ComponentModule {}
|
||||
124
src/component/component.service.ts
Normal file
124
src/component/component.service.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Component } from './component.entity';
|
||||
import { ToolsService } from 'src/utils/tool.service';
|
||||
import { isNumber, omit, pick } from 'lodash';
|
||||
import { ComponentDto } from './component.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ComponentService {
|
||||
constructor(
|
||||
@InjectRepository(Component)
|
||||
private readonly userRepository: Repository<Component>,
|
||||
private readonly toolsService: ToolsService,
|
||||
) {}
|
||||
|
||||
async all(): Promise<Component[]> {
|
||||
const components = await this.userRepository
|
||||
.createQueryBuilder('component')
|
||||
.getMany();
|
||||
return components;
|
||||
}
|
||||
|
||||
async page({
|
||||
pageNo,
|
||||
pageSize,
|
||||
...args
|
||||
}: PageParams<Component>): Promise<Page<Component>> {
|
||||
const hasOwnEntity = new Component();
|
||||
|
||||
const [wheres, likes] = [['is_deleted'], ['name']] as Array<
|
||||
Array<keyof Component>
|
||||
>;
|
||||
|
||||
const [likeWhereSql, likeWhereValue] =
|
||||
this.toolsService.getLikeWhere<Component>(
|
||||
'component',
|
||||
wheres,
|
||||
likes,
|
||||
pick({ ...args, is_deleted: false }, ...wheres, ...likes),
|
||||
);
|
||||
|
||||
const [list, total] = await this.userRepository
|
||||
.createQueryBuilder('component')
|
||||
.select([
|
||||
'component.id',
|
||||
'component.name',
|
||||
'component.type',
|
||||
'component.componentType',
|
||||
'component.image',
|
||||
'component.createTime',
|
||||
])
|
||||
.where(likeWhereSql, likeWhereValue)
|
||||
.andWhere(
|
||||
omit(
|
||||
pick(
|
||||
args,
|
||||
Object.keys(args).filter(
|
||||
(key) =>
|
||||
Object.hasOwn(hasOwnEntity, key) &&
|
||||
(isNumber(args[key]) ? true : !!args[key]),
|
||||
),
|
||||
),
|
||||
...wheres,
|
||||
...likes,
|
||||
),
|
||||
)
|
||||
.skip((pageNo - 1) * pageSize)
|
||||
.take(pageSize)
|
||||
.getManyAndCount();
|
||||
|
||||
return this.toolsService.page<Component>(
|
||||
list.map((component) => new ComponentDto(component)),
|
||||
total,
|
||||
);
|
||||
}
|
||||
|
||||
async save(component: Component): Promise<Component> {
|
||||
const link = this.userRepository.create(component);
|
||||
const save = await this.userRepository.save(link);
|
||||
return save;
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<boolean> {
|
||||
const link = await this.userRepository
|
||||
.createQueryBuilder('component')
|
||||
.update()
|
||||
.set({ is_deleted: true } as any)
|
||||
.where('id = :id', { id })
|
||||
.execute();
|
||||
|
||||
return link.affected > 0;
|
||||
}
|
||||
|
||||
async update(component: Component): Promise<boolean> {
|
||||
const link = await this.userRepository
|
||||
.createQueryBuilder('component')
|
||||
.update()
|
||||
.set(component)
|
||||
.where('id = :id', { id: component.id })
|
||||
.execute();
|
||||
|
||||
return link.affected > 0;
|
||||
}
|
||||
|
||||
async find(id: number): Promise<Component> {
|
||||
const component = await this.userRepository
|
||||
.createQueryBuilder('component')
|
||||
.select([
|
||||
'component.id',
|
||||
'component.name',
|
||||
'component.type',
|
||||
'component.componentType',
|
||||
'component.image',
|
||||
'component.template',
|
||||
'component.createTime',
|
||||
])
|
||||
.where('component.id = :id', {
|
||||
id,
|
||||
})
|
||||
.getOne();
|
||||
return component;
|
||||
}
|
||||
}
|
||||
44
src/dict/dict.controller.ts
Normal file
44
src/dict/dict.controller.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { Controller, Get, HttpStatus, ParseIntPipe, Query, Res } from '@nestjs/common';
|
||||
import { ToolsService } from 'src/utils/tool.service';
|
||||
import { DictService } from './dict.service';
|
||||
import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||
import { ComponentTypeEnum, DictKeyEnum, DictKeyType } from '@/utils/constant';
|
||||
|
||||
@ApiTags('dict')
|
||||
@Controller('dict')
|
||||
export class DictController {
|
||||
constructor(
|
||||
private readonly toolsService: ToolsService,
|
||||
private readonly dictService: DictService,
|
||||
) {} //注入服务
|
||||
|
||||
@ApiOperation({ summary: '根据key获取字典' })
|
||||
@ApiQuery({ name: 'dictKey', enum: DictKeyEnum })
|
||||
@Get('getDictByKey')
|
||||
async getDictByKey(@Res() res, @Query('dictKey') dictKey: DictKeyType) {
|
||||
const dict = this.toolsService.getDictByKey(dictKey)
|
||||
|
||||
return res.send(
|
||||
this.toolsService.res(
|
||||
HttpStatus.OK,
|
||||
'操作成功',
|
||||
dict,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: '根据组件类型获取组件字典' })
|
||||
@ApiQuery({ name: 'type', enum: ComponentTypeEnum })
|
||||
@Get('getComponentDictByType')
|
||||
async getComponentDictByType(@Res() res, @Query('type', ParseIntPipe) type) {
|
||||
const dict = await this.dictService.getComponentDictByType(type)
|
||||
|
||||
return res.send(
|
||||
this.toolsService.res(
|
||||
HttpStatus.OK,
|
||||
'操作成功',
|
||||
dict,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
11
src/dict/dict.module.ts
Normal file
11
src/dict/dict.module.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DictController } from './dict.controller';
|
||||
import { DictService } from './dict.service';
|
||||
import { ToolsService } from 'src/utils/tool.service';
|
||||
|
||||
@Module({
|
||||
controllers: [DictController],
|
||||
providers: [DictService, ToolsService],
|
||||
exports: [DictService],
|
||||
})
|
||||
export class DictModule {}
|
||||
21
src/dict/dict.service.ts
Normal file
21
src/dict/dict.service.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ToolsService } from '@/utils/tool.service';
|
||||
import { ComponentTypeEnum, DictKeyEnum } from '@/utils/constant';
|
||||
|
||||
@Injectable()
|
||||
export class DictService {
|
||||
constructor(private readonly toolsService: ToolsService) {}
|
||||
|
||||
async getComponentDictByType(type: ComponentTypeEnum): Promise<Dict[]> {
|
||||
switch (type) {
|
||||
case ComponentTypeEnum.CHART:
|
||||
return this.toolsService.getDictByKey(DictKeyEnum.CHART);
|
||||
|
||||
case ComponentTypeEnum.COMPONENT:
|
||||
return this.toolsService.getDictByKey(DictKeyEnum.COMPONENT);
|
||||
|
||||
default:
|
||||
return this.toolsService.getDictByKey(DictKeyEnum.CHART);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/main.ts
Normal file
29
src/main.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||
import { urlencoded, json } from 'express';
|
||||
import { knife4jSetup } from 'nestjs-knife4j-plus';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.use(json({ limit: '50mb' }));
|
||||
app.use(urlencoded({ extended: true, limit: '50mb' }));
|
||||
|
||||
const options = new DocumentBuilder()
|
||||
.setTitle('KT-Template API')
|
||||
.setVersion('1.0')
|
||||
.build();
|
||||
const document = SwaggerModule.createDocument(app, options);
|
||||
SwaggerModule.setup('api', app, document);
|
||||
|
||||
// 启用knife4j增强(关键代码)
|
||||
knife4jSetup(app, [
|
||||
{
|
||||
name: '1.0', // 文档版本名称
|
||||
url: `/api-json`, // Swagger openapi JSON地址
|
||||
},
|
||||
]);
|
||||
|
||||
await app.listen(48085);
|
||||
}
|
||||
bootstrap();
|
||||
10
src/middleware/save.middleware.ts
Normal file
10
src/middleware/save.middleware.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
@Injectable()
|
||||
export class SaveMiddleware implements NestMiddleware {
|
||||
use(req: Request, res: Response, next: VoidFunction) {
|
||||
Reflect.deleteProperty(req.body, 'id');
|
||||
next();
|
||||
}
|
||||
}
|
||||
16
src/minio/minio.controller.ts
Normal file
16
src/minio/minio.controller.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {
|
||||
Controller,
|
||||
|
||||
} from '@nestjs/common';
|
||||
import { ToolsService } from 'src/utils/tool.service';
|
||||
import { MinioClientService } from './minio.service';
|
||||
|
||||
@Controller('minio')
|
||||
export class MinioClientController {
|
||||
constructor(
|
||||
private readonly toolsService: ToolsService,
|
||||
private readonly minioClientService: MinioClientService,
|
||||
) {} //注入服务
|
||||
|
||||
|
||||
}
|
||||
13
src/minio/minio.module.ts
Normal file
13
src/minio/minio.module.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MinioClientController } from './minio.controller';
|
||||
import { MinioClientService } from './minio.service';
|
||||
import { ToolsService } from 'src/utils/tool.service';
|
||||
import { MinioModule } from 'nestjs-minio-client';
|
||||
|
||||
@Module({
|
||||
imports: [MinioModule],
|
||||
controllers: [MinioClientController],
|
||||
providers: [MinioClientService, ToolsService],
|
||||
exports: [MinioClientService],
|
||||
})
|
||||
export class MinioClientModule {}
|
||||
11
src/minio/minio.service.ts
Normal file
11
src/minio/minio.service.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ToolsService } from 'src/utils/tool.service';
|
||||
@Injectable()
|
||||
export class MinioClientService {
|
||||
constructor(
|
||||
private readonly toolsService: ToolsService,
|
||||
) {}
|
||||
|
||||
|
||||
}
|
||||
22
src/types/res.d.ts
vendored
Normal file
22
src/types/res.d.ts
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
type Res = {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: any;
|
||||
};
|
||||
|
||||
type Page<T = any> = {
|
||||
list: T[];
|
||||
total: number;
|
||||
};
|
||||
|
||||
type Dict<T = object> = {
|
||||
label: string;
|
||||
value: any;
|
||||
} & Partial<T>;
|
||||
|
||||
type PageParams<T> = {
|
||||
pageSize: number;
|
||||
pageNo: number;
|
||||
} & Partial<T>;
|
||||
|
||||
type Many<T> = T | readonly T[];
|
||||
179
src/utils/constant.ts
Normal file
179
src/utils/constant.ts
Normal file
@ -0,0 +1,179 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class PaginatedDto {
|
||||
@ApiProperty()
|
||||
total: number;
|
||||
}
|
||||
|
||||
export enum ComponentTypeEnum {
|
||||
CHART = 1,
|
||||
COMPONENT = 2,
|
||||
}
|
||||
|
||||
export enum ComponentEnum {
|
||||
NOT_CATEGORY = -1,
|
||||
LINE = 1,
|
||||
BAR = 2,
|
||||
PIE = 3,
|
||||
SCATTER = 4,
|
||||
MAP = 5,
|
||||
CANDLESTICK = 6,
|
||||
RADAR = 7,
|
||||
BOX_PLOT = 8,
|
||||
HEATMAP = 9,
|
||||
GRAPH = 10,
|
||||
LINES = 11,
|
||||
TREE = 12,
|
||||
TREE_MAP = 13,
|
||||
SUNBURST = 14,
|
||||
PARALLEL = 15,
|
||||
SAN_KEY = 16,
|
||||
FUNNEL = 17,
|
||||
GAUGE = 18,
|
||||
PICTORIAL_BAR = 19,
|
||||
THEME_RIVER = 20,
|
||||
LIQUID_FILL = 21,
|
||||
WORD_CLOUD = 22,
|
||||
TABLE = 23,
|
||||
FORM = 24,
|
||||
CONTAINER = 25,
|
||||
}
|
||||
|
||||
export enum DictKeyEnum {
|
||||
COMPONENT_TYPE = 'COMPONENT_TYPE',
|
||||
CHART = 'CHART',
|
||||
COMPONENT = 'COMPONENT',
|
||||
}
|
||||
|
||||
export type DictKeyType = keyof typeof DictKeyEnum;
|
||||
|
||||
export const DictKeyMap: Map<DictKeyType, Dict[]> = new Map();
|
||||
|
||||
const ComponentTypeDict = [
|
||||
{
|
||||
label: '图表',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '组件',
|
||||
value: 2,
|
||||
},
|
||||
];
|
||||
|
||||
const ChartDict = [
|
||||
{
|
||||
label: '未分类',
|
||||
value: -1,
|
||||
},
|
||||
{
|
||||
label: '折线图',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '柱状图',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '饼图',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: '散点图',
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
label: '地图',
|
||||
value: 5,
|
||||
},
|
||||
{
|
||||
label: 'K线图',
|
||||
value: 6,
|
||||
},
|
||||
{
|
||||
label: '雷达图',
|
||||
value: 7,
|
||||
},
|
||||
{
|
||||
label: '盒须图',
|
||||
value: 8,
|
||||
},
|
||||
{
|
||||
label: '热力图',
|
||||
value: 9,
|
||||
},
|
||||
{
|
||||
label: '关系图',
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
label: '路径图',
|
||||
value: 11,
|
||||
},
|
||||
{
|
||||
label: '树图',
|
||||
value: 12,
|
||||
},
|
||||
{
|
||||
label: '矩树图',
|
||||
value: 13,
|
||||
},
|
||||
{
|
||||
label: '旭日图',
|
||||
value: 14,
|
||||
},
|
||||
{
|
||||
label: '平行坐标系',
|
||||
value: 15,
|
||||
},
|
||||
{
|
||||
label: '桑基图',
|
||||
value: 16,
|
||||
},
|
||||
{
|
||||
label: '漏斗图',
|
||||
value: 17,
|
||||
},
|
||||
{
|
||||
label: '仪表盘',
|
||||
value: 18,
|
||||
},
|
||||
{
|
||||
label: '象形图',
|
||||
value: 19,
|
||||
},
|
||||
{
|
||||
label: '河流图',
|
||||
value: 20,
|
||||
},
|
||||
{
|
||||
label: '水球',
|
||||
value: 21,
|
||||
},
|
||||
{
|
||||
label: '词云',
|
||||
value: 22,
|
||||
},
|
||||
];
|
||||
|
||||
const ComponentDict = [
|
||||
{
|
||||
label: '未分类',
|
||||
value: -1,
|
||||
},
|
||||
{
|
||||
label: '表格',
|
||||
value: 23,
|
||||
},
|
||||
{
|
||||
label: '表单',
|
||||
value: 24,
|
||||
},
|
||||
{
|
||||
label: '容器',
|
||||
value: 25,
|
||||
},
|
||||
];
|
||||
|
||||
DictKeyMap.set(DictKeyEnum.COMPONENT_TYPE, ComponentTypeDict);
|
||||
DictKeyMap.set(DictKeyEnum.CHART, ChartDict);
|
||||
DictKeyMap.set(DictKeyEnum.COMPONENT, ComponentDict);
|
||||
105
src/utils/tool.service.ts
Normal file
105
src/utils/tool.service.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import * as svgCaptcha from 'svg-captcha';
|
||||
import { DictKeyMap, DictKeyEnum } from './constant';
|
||||
import type { DictKeyType } from './constant';
|
||||
import { isBoolean } from 'lodash';
|
||||
|
||||
@Injectable()
|
||||
export class ToolsService {
|
||||
async captche(size = 4) {
|
||||
const captcha = svgCaptcha.create({
|
||||
//可配置返回的图片信息
|
||||
size, //生成几个验证码
|
||||
fontSize: 50, //文字大小
|
||||
width: 100, //宽度
|
||||
height: 34, //高度
|
||||
background: '#ffffff', //背景颜色
|
||||
});
|
||||
return captcha;
|
||||
}
|
||||
|
||||
res(code: number, msg: string, data: any): Res {
|
||||
const retn: Res = {
|
||||
code,
|
||||
msg,
|
||||
data,
|
||||
};
|
||||
return retn;
|
||||
}
|
||||
|
||||
page<T = any>(list: T[], total: number): Page<T> {
|
||||
const retn = {
|
||||
list,
|
||||
total,
|
||||
};
|
||||
return retn;
|
||||
}
|
||||
|
||||
getWhereStr(alias: string, key) {
|
||||
return `${alias}.${key.toString()} = :${key.toString()}`;
|
||||
}
|
||||
|
||||
getLikeStr(alias: string, key) {
|
||||
return `${alias}.${key.toString()} like :${key.toString()}`;
|
||||
}
|
||||
|
||||
getLikeWhere<T = object>(
|
||||
alias: string,
|
||||
wheres: Array<keyof T>,
|
||||
likes: Array<keyof T>,
|
||||
values: Partial<T>,
|
||||
operator = 'AND',
|
||||
): [string, Record<keyof T, string>] {
|
||||
const linkOperator = ` ${operator} `;
|
||||
const wheresEndIndex = wheres.length;
|
||||
|
||||
return [
|
||||
[...wheres, ...likes].reduce((pre, cur, index, source) => {
|
||||
const isLink = !!source
|
||||
.slice(0, index)
|
||||
.some((key) => isBoolean(values[key]) || !!values[key]);
|
||||
|
||||
const { getLikeStr, getWhereStr } = this;
|
||||
|
||||
if (!isBoolean(values[cur]) && !values[cur]) return pre;
|
||||
|
||||
if (!index) getWhereStr(alias, cur);
|
||||
|
||||
const matchSqlFn = index >= wheresEndIndex ? getLikeStr : getWhereStr;
|
||||
const beforeSql = `${pre}${isLink ? linkOperator : ' '}`;
|
||||
|
||||
return `${beforeSql}${matchSqlFn(alias, cur)}`;
|
||||
}, ''),
|
||||
Object.entries(values).reduce((pre, [key, value]) => {
|
||||
if (!isBoolean(value) && !value) return pre;
|
||||
|
||||
if (likes.includes(key as keyof T))
|
||||
return { ...pre, ...{ [key]: `%${value}%` } };
|
||||
|
||||
return { ...pre, ...{ [key]: value } };
|
||||
}, {} as Record<keyof T, string>),
|
||||
];
|
||||
}
|
||||
|
||||
getDictByKey(key: DictKeyType): Dict[] {
|
||||
if (!DictKeyEnum[key]) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return DictKeyMap.get(key);
|
||||
}
|
||||
|
||||
dictFormat<T = object>(
|
||||
label: string,
|
||||
value: any,
|
||||
other: Partial<T>,
|
||||
): Dict<T> {
|
||||
const options = {
|
||||
label,
|
||||
value,
|
||||
...other,
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
24
test/app.e2e-spec.ts
Normal file
24
test/app.e2e-spec.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import * as request from 'supertest';
|
||||
import { AppModule } from './../src/app.module';
|
||||
|
||||
describe('AppController (e2e)', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
beforeEach(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('/ (GET)', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/')
|
||||
.expect(200)
|
||||
.expect('Hello World!');
|
||||
});
|
||||
});
|
||||
9
test/jest-e2e.json
Normal file
9
test/jest-e2e.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"moduleFileExtensions": ["js", "json", "ts"],
|
||||
"rootDir": ".",
|
||||
"testEnvironment": "node",
|
||||
"testRegex": ".e2e-spec.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
}
|
||||
}
|
||||
4
tsconfig.build.json
Normal file
4
tsconfig.build.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
}
|
||||
25
tsconfig.json
Normal file
25
tsconfig.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ESNext",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user