gemercheung 7 ay önce
ebeveyn
işleme
0cde9b8ba3

+ 5 - 1
packages/backend/src/app.module.ts

@@ -16,6 +16,7 @@ import { AuthModule } from './modules/auth/auth.module';
 import { ArticleModule } from './modules/article/article.module';
 import { CategoryModule } from './modules/category/category.module';
 import { LoggerModule } from 'nestjs-pino';
+import { MenuModule } from './modules/menu/menu.module';
 @Module({
   imports: [
     LoggerModule.forRoot(),
@@ -35,7 +36,10 @@ import { LoggerModule } from 'nestjs-pino';
     ArticleModule,
 
     CategoryModule,
+
+    MenuModule,
   ],
   controllers: [],
+  providers: [],
 })
-export class AppModule { }
+export class AppModule {}

+ 1 - 1
packages/backend/src/modules/article/article.controller.ts

@@ -19,7 +19,7 @@ import { CreateArticleDto, GetArticleDto, QueryArticleDto, UpdateArticleDto } fr
 @ApiBearerAuth('JWT')
 @UseGuards(JwtGuard)
 export class ArticleController {
-  constructor(private readonly articleService: ArticleService) { }
+  constructor(private readonly articleService: ArticleService) {}
 
   @Post()
   create(@Body() createArticleDto: CreateArticleDto) {

+ 19 - 0
packages/backend/src/modules/article/article.entity.ts

@@ -5,6 +5,8 @@ import {
   JoinColumn,
   JoinTable,
   ManyToMany,
+  ManyToOne,
+  OneToMany,
   OneToOne,
   PrimaryGeneratedColumn,
   UpdateDateColumn,
@@ -58,6 +60,23 @@ export class Article {
   @UpdateDateColumn()
   updateTime: Date;
 
+  @OneToMany(() => ArticleContent, (articleContent) => articleContent.content)
+  public contents: ArticleContent[];
+}
 
+@Entity()
+export class ArticleContent {
+  @PrimaryGeneratedColumn()
+  id: number;
 
+  @ManyToOne(() => Article, (article) => article.contents, {
+    createForeignKeyConstraints: false,
+  })
+  article: Article;
+
+  @Column({ unique: false, default: '', length: 200 })
+  lang: string;
+
+  @Column({ type: 'longtext', nullable: true })
+  content: string;
 }

+ 3 - 3
packages/backend/src/modules/article/article.module.ts

@@ -1,11 +1,11 @@
 import { Module } from '@nestjs/common';
 import { ArticleService } from './article.service';
 import { ArticleController } from './article.controller';
-import { Article } from './article.entity';
+import { Article, ArticleContent } from './article.entity';
 import { TypeOrmModule } from '@nestjs/typeorm';
 @Module({
-  imports: [TypeOrmModule.forFeature([Article])],
+  imports: [TypeOrmModule.forFeature([Article, ArticleContent])],
   providers: [ArticleService],
   controllers: [ArticleController],
 })
-export class ArticleModule {}
+export class ArticleModule { }

+ 1 - 1
packages/backend/src/modules/article/article.service.ts

@@ -8,7 +8,7 @@ export class ArticleService {
   constructor(
     @InjectRepository(Article)
     private articleRepo: Repository<Article>,
-  ) { }
+  ) {}
   async create(createArticleDto: CreateArticleDto) {
     const article = this.articleRepo.create(createArticleDto);
     return this.articleRepo.save(article);

+ 1 - 1
packages/backend/src/modules/article/dto.ts

@@ -61,7 +61,7 @@ export class GetArticleDto {
   enable?: boolean;
 }
 
-export class QueryArticleDto extends GetArticleDto { }
+export class QueryArticleDto extends GetArticleDto {}
 
 export class UpdateArticleDto {
   @ApiProperty()

+ 1 - 1
packages/backend/src/modules/category/category.service.ts

@@ -8,7 +8,7 @@ export class CategoryService {
   constructor(
     @InjectRepository(Category)
     private categoryRepo: Repository<Category>,
-  ) { }
+  ) {}
 
   async create(createCategoryDto: CreateCategoryDto) {
     const category = this.categoryRepo.create(createCategoryDto);

+ 1 - 2
packages/backend/src/modules/category/dto.ts

@@ -64,5 +64,4 @@ export class GetAllCategoryDto {
   enable?: boolean;
 }
 
-export class UpdateCategoryDto extends PartialType(CreateCategoryDto) {
-}
+export class UpdateCategoryDto extends PartialType(CreateCategoryDto) {}

+ 94 - 0
packages/backend/src/modules/menu/dto.ts

@@ -0,0 +1,94 @@
+import { ApiProperty, ApiBody, PartialType } from '@nestjs/swagger';
+import { Exclude } from 'class-transformer';
+import {
+  Allow,
+  IsArray,
+  IsBoolean,
+  IsNotEmpty,
+  IsNumber,
+  IsOptional,
+  IsString,
+  Length,
+} from 'class-validator';
+
+export class CreateMenuDto {
+  @ApiProperty()
+  @IsString()
+  @IsNotEmpty({ message: '标题不能为空' })
+  @Length(1, 200, {
+    message: `用户名长度必须大于$constraint1到$constraint2之间,当前传递的值是$value`,
+  })
+  title: string;
+
+  @ApiProperty({ required: true })
+  @IsString()
+  content: string;
+
+  @ApiProperty({ required: true })
+  @IsNumber()
+  userId: number;
+
+  @ApiProperty({ required: true })
+  @IsNumber()
+  categoryId: number;
+
+  @ApiProperty({ required: false, default: true })
+  @IsBoolean()
+  @IsOptional()
+  isPublish?: boolean;
+
+  @ApiProperty({ required: false })
+  @IsBoolean()
+  @IsOptional()
+  enable?: boolean;
+}
+
+export class GetArticleDto {
+  @ApiProperty({ required: false })
+  @Allow()
+  pageSize?: number;
+
+  @ApiProperty({ required: false })
+  @Allow()
+  pageNo?: number;
+
+  @ApiProperty({ required: false })
+  @Allow()
+  title?: string;
+
+  @ApiProperty({ required: false })
+  @Allow()
+  enable?: boolean;
+}
+
+export class QueryArticleDto extends GetArticleDto {}
+
+export class UpdateArticleDto {
+  @ApiProperty()
+  @IsString()
+  @IsOptional()
+  @Length(1, 200, {
+    message: `用户名长度必须大于$constraint1到$constraint2之间,当前传递的值是$value`,
+  })
+  title?: string;
+
+  @ApiProperty({ required: false })
+  @IsOptional()
+  @IsString()
+  content?: string;
+
+  @ApiProperty({ required: false })
+  @IsBoolean()
+  @IsOptional()
+  enable?: boolean;
+
+  @ApiProperty({ required: false })
+  @IsOptional()
+  @IsNumber()
+  userId?: number;
+
+  @ApiProperty({ required: false })
+  @IsOptional()
+  @IsNumber()
+  categoryId?: number;
+}

+ 4 - 0
packages/backend/src/modules/menu/menu.controller.ts

@@ -0,0 +1,4 @@
+import { Controller } from '@nestjs/common';
+
+@Controller('menu')
+export class MenuController {}

+ 81 - 0
packages/backend/src/modules/menu/menu.entity.ts

@@ -0,0 +1,81 @@
+import {
+  Column,
+  CreateDateColumn,
+  Entity,
+  JoinColumn,
+  JoinTable,
+  ManyToMany,
+  ManyToOne,
+  OneToMany,
+  OneToOne,
+  PrimaryGeneratedColumn,
+  UpdateDateColumn,
+} from 'typeorm';
+import { Category } from '../category/category.entity';
+import { User } from '../user/user.entity';
+
+@Entity()
+export class Menu {
+  @PrimaryGeneratedColumn()
+  id: number;
+
+  @Column({ unique: false, default: '', length: 200 })
+  title: string;
+
+  @Column({ default: true })
+  enable: boolean;
+
+  @Column({ default: 0 })
+  level: number;
+
+  @Column({ default: '' })
+  cover: string;
+
+  @Column({ default: false })
+  isPublish: boolean;
+
+  @Column({ type: 'longtext', nullable: true })
+  content: string;
+
+  @Column({ default: '' })
+  remark: string;
+
+  @OneToOne(() => Category, {
+    cascade: true,
+    onDelete: 'CASCADE',
+    createForeignKeyConstraints: false,
+  })
+  @JoinColumn()
+  category: Category;
+
+  @Column({ nullable: true })
+  categoryId: number;
+
+  @OneToOne(() => User, {
+    cascade: true,
+  })
+  @JoinColumn()
+  user: User;
+
+  @Column({ nullable: true })
+  userId: number;
+
+  @CreateDateColumn()
+  createTime: Date;
+
+  @UpdateDateColumn()
+  updateTime: Date;
+
+  @ManyToOne(() => Menu, (menu) => menu.children, {
+    createForeignKeyConstraints: false,
+  })
+  parent: Menu;
+
+  @OneToMany(() => Menu, (menu) => menu.parent, {
+    createForeignKeyConstraints: false,
+  })
+  children: Menu[];
+
+  @Column({ nullable: true })
+  parentId: number;
+}

+ 11 - 0
packages/backend/src/modules/menu/menu.module.ts

@@ -0,0 +1,11 @@
+import { Module } from '@nestjs/common';
+import { MenuController } from './menu.controller';
+import { MenuService } from './menu.service';
+import { Menu } from './menu.entity';
+import { TypeOrmModule } from '@nestjs/typeorm';
+@Module({
+  imports: [TypeOrmModule.forFeature([Menu])],
+  controllers: [MenuController],
+  providers: [MenuService],
+})
+export class MenuModule {}

+ 18 - 0
packages/backend/src/modules/menu/menu.service.ts

@@ -0,0 +1,18 @@
+import { Injectable } from '@nestjs/common';
+import { InjectRepository } from '@nestjs/typeorm';
+import { Menu } from './menu.entity';
+import { Repository } from 'typeorm';
+import { CreateMenuDto } from './dto';
+
+@Injectable()
+export class MenuService {
+  constructor(
+    @InjectRepository(Menu)
+    private menuRepo: Repository<Menu>,
+  ) {}
+
+  async create(createMenuDto: CreateMenuDto) {
+    const menu = this.menuRepo.create(createMenuDto);
+    return this.menuRepo.save(menu);
+  }
+}

+ 9 - 9
packages/frontend/src/views/article/edit.vue

@@ -7,31 +7,31 @@
     </template>
 
     <div class="editor-wrap">
-      <n-form
+      <n-form
         ref="modalFormRef" class="form wh-full" label-placement="left" label-align="left" :label-width="80"
-        :model="modalForm"
+        :model="modalForm"
       >
-        <n-form-item
+        <n-form-item
           label="文章名称" path="title" :rule="{
             required: true,
             message: '请输入文章名称',
             trigger: ['input', 'blur'],
-          }"
+          }"
         >
           <n-input v-model:value="modalForm.title" />
         </n-form-item>
 
-        <n-form-item
+        <n-form-item
           label="文章分类" path="categoryId" :rule="{
             required: true,
             type: 'number',
             trigger: ['change', 'blur'],
             message: '请输入文章分类',
-          }"
+          }"
         >
-          <n-select
+          <n-select
             v-model:value="modalForm.categoryId" :options="allCategory" clearable filterable tag
-            style="max-width: 300px;"
+            style="max-width: 300px;"
           />
         </n-form-item>
 
@@ -85,7 +85,7 @@ categoryApi.getAll().then(({ data = [] }) => (allCategory.value = data.map(item
 function handleEdit() {
   modalFormRef.value?.validate((errors) => {
     if (!errors) {
-      articleApi.u(modalForm.value)
+      articleApi.update(modalForm.value)
       $message.success('保存成功!')
       router.push('/article')
     }

+ 41 - 0
packages/frontend/src/views/menu/index.vue

@@ -0,0 +1,41 @@
+<template>
+  <CommonPage>
+    <template #action>
+      <NButton type="primary" @click="router.push('article/add')">
+        <i class="i-material-symbols:add mr-4 text-18" />
+        新增一级菜单
+      </NButton>
+    </template>
+
+    <n-flex class="flex" justify="justify-center" size="large">
+      <n-card class="min-w-200 min-h-220 w-30%" title="新手入门">
+        <div class="ml-20 flex-col">
+          xxx
+        </div>
+        <template #header-extra>
+          <n-dropdown trigger="click" :options="options" :show-arrow="true" @select="handleSelect">
+            <n-button text>
+              <i class="i-material-symbols:more-horiz text-24" />
+            </n-button>
+          </n-dropdown>
+        </template>
+      </n-card>
+    </n-flex>
+  </CommonPage>
+</template>
+
+<script setup>
+const options = [
+  {
+    label: '编辑',
+    key: 'edit',
+  },
+  {
+    label: '删除',
+    key: 'delete',
+  },
+]
+function handleSelect() {
+
+}
+</script>