浏览代码

feat: save

gemercheung 6 月之前
父节点
当前提交
54d9072f5a

+ 18 - 0
packages/backend/src/modules/category/category.entity.translation.ts

@@ -0,0 +1,18 @@
+import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
+import { TranslationEntity } from 'typeorm-translatable';
+import { Category } from './category.entity';
+
+@Entity()
+export class CategoryTranslation extends TranslationEntity<Category> {
+  @Column({ unique: false, default: '', length: 200 })
+  title?: string;
+
+  @Column({ type: 'longtext', nullable: true })
+  remark?: string;
+
+  @ManyToOne(() => Category, (category) => category.translations, {
+    // onDelete: 'CASCADE',
+    createForeignKeyConstraints: false,
+  })
+  source?: Category | undefined;
+}

+ 10 - 2
packages/backend/src/modules/category/category.entity.ts

@@ -12,9 +12,10 @@ import {
   UpdateDateColumn,
 } from 'typeorm';
 import { User } from '../user/user.entity';
-
+import { TranslatableEntity, Translation } from 'typeorm-translatable';
+import { CategoryTranslation } from './category.entity.translation';
 @Entity()
-export class Category {
+export class Category extends TranslatableEntity<CategoryTranslation> {
   @PrimaryGeneratedColumn()
   id: number;
 
@@ -55,4 +56,11 @@ export class Category {
 
   @UpdateDateColumn()
   updateTime: Date;
+
+  @OneToMany(() => CategoryTranslation, (articleTranslation) => articleTranslation.source, {
+    cascade: true,
+  })
+  translations?: Translation<CategoryTranslation>[] | undefined;
+
+  static translatableFields = new Set(['title', 'remark']);
 }

+ 3 - 2
packages/backend/src/modules/category/category.module.ts

@@ -3,9 +3,10 @@ import { CategoryController } from './category.controller';
 import { CategoryService } from './category.service';
 import { Category } from './category.entity';
 import { TypeOrmModule } from '@nestjs/typeorm';
+import { CategoryTranslation } from './category.entity.translation';
 @Module({
-  imports: [TypeOrmModule.forFeature([Category])],
+  imports: [TypeOrmModule.forFeature([Category, CategoryTranslation])],
   controllers: [CategoryController],
   providers: [CategoryService],
 })
-export class CategoryModule {}
+export class CategoryModule { }

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

@@ -11,7 +11,7 @@ export class CategoryService {
     @InjectRepository(Category)
     private categoryRepo: Repository<Category>,
     private readonly sharedService: SharedService,
-  ) {}
+  ) { }
 
   async create(createCategoryDto: CreateCategoryDto) {
     const category = this.categoryRepo.create(createCategoryDto);
@@ -36,7 +36,7 @@ export class CategoryService {
         enable: query.enable || undefined,
       },
       // children: true
-      relations: { parent: true, user: true },
+      relations: { parent: true, user: true, translations: true },
       order: {
         // title: 'ASC',
         createTime: 'DESC',

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

@@ -56,6 +56,29 @@ export class GetCategoryDto {
   @ApiProperty({ required: false })
   @Allow()
   enable?: boolean;
+
+  @ApiProperty({ required: false })
+  @IsArray()
+  @IsOptional()
+  translations?: CreateCategoryTranslations[];
+}
+export class CreateCategoryTranslations {
+  @ApiProperty({ required: false })
+  @IsString()
+  locale: string;
+
+  @ApiProperty({ required: false })
+  @IsString()
+  title: string;
+
+  @ApiProperty({ required: false })
+  @IsString()
+  remark: string;
+
+  @ApiProperty({ required: false })
+  @IsString()
+  @IsOptional()
+  sourceId?: number;
 }
 
 export class GetAllCategoryDto {
@@ -64,4 +87,4 @@ export class GetAllCategoryDto {
   enable?: boolean;
 }
 
-export class UpdateCategoryDto extends PartialType(CreateCategoryDto) {}
+export class UpdateCategoryDto extends PartialType(CreateCategoryDto) { }

+ 1 - 0
packages/frontend/src/utils/translations.js

@@ -19,4 +19,5 @@ export function initTranslations(object, keys) {
     }
     return mapper
   })
+  return object
 }

+ 36 - 14
packages/frontend/src/views/category/index.vue

@@ -14,24 +14,20 @@
         </n-input>
       </MeQueryItem>
       <MeQueryItem label="状态" :label-width="50">
-        <n-select
-          v-model:value="queryItems.enable" clearable :options="[
-            { label: '启用', value: 1 },
-            { label: '停用', value: 0 },
-          ]"
-        />
+        <n-select v-model:value="queryItems.enable" clearable :options="[
+          { label: '启用', value: 1 },
+          { label: '停用', value: 0 },
+        ]" />
       </MeQueryItem>
     </MeCrud>
 
     <MeModal ref="modalRef" width="520px">
       <n-form ref="modalFormRef" label-placement="left" label-align="left" :label-width="80" :model="modalForm">
-        <n-form-item
-          label="分类名" path="title" :rule="{
-            required: true,
-            message: '请输入分类名',
-            trigger: ['input', 'blur'],
-          }"
-        >
+        <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 label="上层分类" path="parentId">
@@ -40,6 +36,28 @@
         <n-form-item label="备注" path="remark">
           <n-input v-model:value="modalForm.remark" />
         </n-form-item>
+        <!-- {{ modalForm.translations }} -->
+        <n-tabs type="line" v-if="modalForm.translations.length > 0" animated>
+          <template v-for="(lang, index) in langs" :key="lang">
+            <n-tab-pane :name="lang" :tab="langLabel[lang]" :index="index">
+              <n-form-item label="名称" path="title" :rule="{
+                required: true,
+                message: '请输入名称',
+                trigger: ['input', 'blur'],
+              }">
+                <n-input v-model:value="modalForm.translations[index].title" />
+              </n-form-item>
+
+              <n-form-item label="描述" path="description" :rule="{
+                required: false,
+                message: '请输入描述',
+                trigger: ['input', 'blur'],
+              }">
+                <n-input v-model:value="modalForm.translations[index].description" type="textarea" />
+              </n-form-item>
+            </n-tab-pane>
+          </template>
+        </n-tabs>
 
         <n-form-item label="状态" path="enable">
           <NSwitch v-model:value="modalForm.enable">
@@ -63,6 +81,7 @@ import { useUserStore } from '@/store/index.js'
 import { formatDateTime } from '@/utils'
 import { NButton, NSwitch } from 'naive-ui'
 import { onMounted, watchEffect } from 'vue'
+import { initTranslations, langLabel, langs } from '@/utils/translations'
 import api from './api'
 
 defineOptions({ name: 'Category' })
@@ -79,7 +98,10 @@ const { modalRef, modalFormRef, modalAction, modalForm, handleAdd, handleDelete,
     doCreate: api.create,
     doDelete: api.delete,
     doUpdate: api.update,
-    initForm: { enable: true },
+    initForm: {
+      enable: true,
+      translations: initTranslations({}, ['title', 'remark']).translations,
+    },
     refresh: (_, keepCurrentPage) => $table.value?.handleSearch(keepCurrentPage),
   })
 onMounted(() => {