chore: initialize application scaffold

This commit is contained in:
2026-08-23 19:55:59 +08:00
parent 36088ce005
commit 6591e3fa1e
36 changed files with 7771 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
<div id="app"></div><script type="module" src="/src/main.ts"></script>
+19
View File
@@ -0,0 +1,19 @@
{
"name": "@example/admin",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vite --port 5174",
"build": "vue-tsc -b && vite build"
},
"dependencies": {
"@arco-design/web-vue": "^2.57.0",
"vue": "^3.5.13"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"typescript": "^5.7.2",
"vite": "^6.0.5",
"vue-tsc": "^2.2.0"
}
}
+7
View File
@@ -0,0 +1,7 @@
<script setup lang="ts">
import AdminDashboard from './pages/AdminDashboard.vue';
</script>
<template>
<AdminDashboard />
</template>
+7
View File
@@ -0,0 +1,7 @@
import { createApp } from 'vue';
import ArcoVue from '@arco-design/web-vue';
import '@arco-design/web-vue/dist/arco.css';
import App from './App.vue';
/** 创建后台管理 Vue 应用实例。 */
createApp(App).use(ArcoVue).mount('#app');
+16
View File
@@ -0,0 +1,16 @@
<script setup lang="ts">
/** 定义仪表盘展示的状态文字。 */
const statusText = '管理端示例已准备就绪';
</script>
<template>
<main class="dashboard-page">
<a-card title="后台管理端示例">
<a-result status="success" :title="statusText" sub-title="后续可在 pages components 中按模块扩展功能" />
</a-card>
</main>
</template>
<style scoped>
.dashboard-page { max-width: 960px; margin: 80px auto; padding: 0 24px; }
</style>
+11
View File
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noEmit": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts", "src/**/*.vue"]
}
+1
View File
@@ -0,0 +1 @@
{"root":["./src/main.ts","./src/app.vue","./src/pages/admindashboard.vue"],"version":"5.9.3"}
+5
View File
@@ -0,0 +1,5 @@
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
/** 定义管理端开发与构建配置。 */
export default defineConfig({ plugins: [vue()] });
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src"
}
+31
View File
@@ -0,0 +1,31 @@
{
"name": "@example/api",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "nest start --watch",
"build": "nest build",
"start": "node dist/main.js"
},
"dependencies": {
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.0",
"@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/typeorm": "^11.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"pg": "^8.13.1",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"typeorm": "^0.3.20"
},
"devDependencies": {
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@types/node": "^22.10.1",
"ts-node": "^10.9.2",
"typescript": "^5.7.2"
}
}
+26
View File
@@ -0,0 +1,26 @@
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from './modules/users/users.module';
/** 聚合应用基础设施与业务模块。 */
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
type: 'postgres' as const,
host: config.getOrThrow<string>('DATABASE_HOST'),
port: Number(config.get<string>('DATABASE_PORT', '5432')),
username: config.getOrThrow<string>('DATABASE_USER'),
password: config.getOrThrow<string>('DATABASE_PASSWORD'),
database: config.getOrThrow<string>('DATABASE_NAME'),
autoLoadEntities: true,
synchronize: false
})
}),
UsersModule
]
})
export class AppModule {}
+14
View File
@@ -0,0 +1,14 @@
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
/** 启动 HTTP 服务并配置全局协议规则。 */
async function bootstrap(): Promise<void> {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api/v1');
app.enableCors({ origin: ['http://localhost:5173', 'http://localhost:5174'] });
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true }));
await app.listen(process.env.API_PORT ?? 3000);
}
void bootstrap();
@@ -0,0 +1,15 @@
import { IsEmail, IsNotEmpty, IsString, MaxLength } from 'class-validator';
/** 创建用户的请求参数。 */
export class CreateUserDto {
/** 用户展示名称。 */
@IsString()
@IsNotEmpty()
@MaxLength(50)
name!: string;
/** 用户邮箱地址。 */
@IsEmail()
@MaxLength(120)
email!: string;
}
@@ -0,0 +1,25 @@
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
/** 用户表对应的领域实体。 */
@Entity({ name: 'users' })
export class UserEntity {
/** 用户主键。 */
@PrimaryGeneratedColumn('uuid')
id!: string;
/** 用户展示名称。 */
@Column({ length: 50 })
name!: string;
/** 用户邮箱地址,作为唯一业务标识。 */
@Column({ length: 120, unique: true })
email!: string;
/** 创建时间。 */
@CreateDateColumn({ name: 'created_at' })
createdAt!: Date;
/** 最后更新时间。 */
@UpdateDateColumn({ name: 'updated_at' })
updatedAt!: Date;
}
@@ -0,0 +1,27 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from '../dto/create-user.dto';
import { UserEntity } from '../entities/user.entity';
/** 封装 users 表的持久化操作。 */
@Injectable()
export class UsersRepository {
/** 初始化用户实体仓储。 */
constructor(@InjectRepository(UserEntity) private readonly repository: Repository<UserEntity>) {}
/** 查询全部用户并按创建时间倒序返回。 */
findAll(): Promise<UserEntity[]> {
return this.repository.find({ order: { createdAt: 'DESC' } });
}
/** 根据邮箱查询单个用户。 */
findByEmail(email: string): Promise<UserEntity | null> {
return this.repository.findOneBy({ email });
}
/** 创建并保存用户实体。 */
async create(data: CreateUserDto): Promise<UserEntity> {
return this.repository.save(this.repository.create(data));
}
}
@@ -0,0 +1,23 @@
import { Body, Controller, Get, Post } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UserEntity } from './entities/user.entity';
import { UsersService } from './users.service';
/** 提供 users 资源的 REST API。 */
@Controller('users')
export class UsersController {
/** 注入用户服务。 */
constructor(private readonly usersService: UsersService) {}
/** 响应用户集合资源。 */
@Get()
findAll(): Promise<UserEntity[]> {
return this.usersService.findAll();
}
/** 创建一条用户资源。 */
@Post()
create(@Body() data: CreateUserDto): Promise<UserEntity> {
return this.usersService.create(data);
}
}
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserEntity } from './entities/user.entity';
import { UsersRepository } from './repositories/users.repository';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
/** 注册用户模块的控制器、服务与数据访问依赖。 */
@Module({
imports: [TypeOrmModule.forFeature([UserEntity])],
controllers: [UsersController],
providers: [UsersService, UsersRepository]
})
export class UsersModule {}
@@ -0,0 +1,25 @@
import { ConflictException, Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UserEntity } from './entities/user.entity';
import { UsersRepository } from './repositories/users.repository';
/** 处理用户领域业务规则。 */
@Injectable()
export class UsersService {
/** 注入用户数据仓储。 */
constructor(private readonly usersRepository: UsersRepository) {}
/** 获取用户列表。 */
findAll(): Promise<UserEntity[]> {
return this.usersRepository.findAll();
}
/** 创建用户,并保证邮箱不重复。 */
async create(data: CreateUserDto): Promise<UserEntity> {
const existingUser = await this.usersRepository.findByEmail(data.email);
if (existingUser) {
throw new ConflictException('邮箱已被使用');
}
return this.usersRepository.create(data);
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2023",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"strict": true,
"skipLibCheck": true
}
}
+1
View File
@@ -0,0 +1 @@
<div id="app"></div><script type="module" src="/src/main.ts"></script>
+19
View File
@@ -0,0 +1,19 @@
{
"name": "@example/web",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vite --port 5173",
"build": "vue-tsc -b && vite build"
},
"dependencies": {
"tdesign-vue-next": "^1.10.3",
"vue": "^3.5.13"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"typescript": "^5.7.2",
"vite": "^6.0.5",
"vue-tsc": "^2.2.0"
}
}
+7
View File
@@ -0,0 +1,7 @@
<script setup lang="ts">
import UserWelcome from './pages/UserWelcome.vue';
</script>
<template>
<UserWelcome />
</template>
+7
View File
@@ -0,0 +1,7 @@
import { createApp } from 'vue';
import TDesign from 'tdesign-vue-next';
import 'tdesign-vue-next/es/style/index.css';
import App from './App.vue';
/** 创建用户端 Vue 应用实例。 */
createApp(App).use(TDesign).mount('#app');
+22
View File
@@ -0,0 +1,22 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
/** 存储当前示例名称。 */
const name = ref('开发者');
/** 生成页面问候语。 */
const greeting = computed(() => `你好,${name.value}。示例用户端已准备就绪。`);
</script>
<template>
<main class="welcome-page">
<t-card title="用户端示例">
<p>{{ greeting }}</p>
<t-input v-model="name" clearable placeholder="请输入名称" />
</t-card>
</main>
</template>
<style scoped>
.welcome-page { max-width: 720px; margin: 80px auto; padding: 0 24px; }
</style>
+11
View File
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noEmit": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts", "src/**/*.vue"]
}
+1
View File
@@ -0,0 +1 @@
{"root":["./src/main.ts","./src/app.vue","./src/pages/userwelcome.vue"],"version":"5.9.3"}
+5
View File
@@ -0,0 +1,5 @@
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
/** 定义用户端开发与构建配置。 */
export default defineConfig({ plugins: [vue()] });