gemercheung 6 місяців тому
батько
коміт
715392dcb8

+ 2 - 1
.gitignore

@@ -1,4 +1,5 @@
 node_modules
 dist
 .vscode
-.idea
+.idea
+docker/html

+ 5 - 3
docker-compose.yml

@@ -7,11 +7,11 @@ services:
     environment:
       NODE_ENV: "production"
       DB_PORT: 3306
-      DB_HOST: "120.24.144.164" # 设置 root 用户密码
+      DB_HOST: "172.18.156.39" # 设置 root 用户密码
       DB_PWD: "4Dage@4Dage#@168"
       DB_USER: "root"
       DB_DATABASE: "4dkankan_motion"
-      REDIS_URL: "redis://127.0.0.1:6379/7"
+      REDIS_URL: "redis://172.18.157.42:6379/7"
     networks:
       - backend
     ports:
@@ -22,8 +22,10 @@ services:
     build:
       dockerfile: docker/frontend/Dockerfile
       context: .
+    volumes:
+      - docker/html:/usr/share/nginx/html
     ports:
-      - "8080:80"
+      - "19090:80"
     networks:
       - backend
 

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

@@ -79,8 +79,30 @@ export class CreateMenuDto {
   @IsString()
   @IsOptional()
   description?: string;
+
+  @ApiProperty({ required: false })
+  @IsArray()
+  @IsOptional()
+  translations?: CreateMenuTranslations[];
 }
+export class CreateMenuTranslations {
+  @ApiProperty({ required: false })
+  @IsString()
+  locale: string;
+
+  @ApiProperty({ required: false })
+  @IsString()
+  title: string;
+
+  @ApiProperty({ required: false })
+  @IsString()
+  description: string;
 
+  @ApiProperty({ required: false })
+  @IsString()
+  @IsOptional()
+  sourceId?: number;
+}
 export class GetMenuDto {
   @ApiProperty({ required: false })
   @Allow()

+ 1 - 1
packages/backend/src/modules/menu/menu.entity.translation.ts

@@ -20,7 +20,7 @@ export class MenuTranslation extends TranslationEntity<Menu> {
   title?: string;
 
   @Column({ default: '' })
-  remark?: string;
+  description?: string;
 
   @ManyToOne(() => Menu, (menu) => menu.translations, {
     onDelete: 'CASCADE',

+ 1 - 2
packages/backend/src/modules/menu/menu.entity.ts

@@ -102,9 +102,8 @@ export class Menu extends TranslatableEntity<MenuTranslation> {
   articleId: number;
   @OneToMany(() => MenuTranslation, (menuTranslation) => menuTranslation.source, {
     cascade: true,
-    onDelete: 'CASCADE',
   })
   translations?: Translation<MenuTranslation>[] | undefined;
 
-  static translatableFields = new Set(['title', 'remark']);
+  static translatableFields = new Set(['title', 'description']);
 }

+ 8 - 3
packages/backend/src/modules/menu/menu.service.ts

@@ -3,16 +3,21 @@ import { InjectRepository } from '@nestjs/typeorm';
 import { Menu } from './menu.entity';
 import { Like, Repository } from 'typeorm';
 import { CreateMenuDto, GetMenuDto, QueryMenuDto, UpdateMenuDto } from './dto';
+import { MenuTranslation } from './menu.entity.translation';
 
 @Injectable()
 export class MenuService {
   constructor(
     @InjectRepository(Menu)
     private menuRepo: Repository<Menu>,
-  ) {}
+
+    @InjectRepository(MenuTranslation)
+    private menuTranslation: Repository<MenuTranslation>,
+  ) { }
 
   async create(createMenuDto: CreateMenuDto) {
     const menu = this.menuRepo.create(createMenuDto);
+    // console.log('menu', menu);
     return this.menuRepo.save(menu);
   }
 
@@ -25,7 +30,7 @@ export class MenuService {
         parentId: query.parentId || undefined,
         enable: query.enable || undefined,
       },
-      relations: { user: true, category: true },
+      relations: { user: true, category: true, translations: true },
       order: {
         // title: 'ASC',
         createTime: 'DESC',
@@ -49,7 +54,7 @@ export class MenuService {
   async findWithChild(id: number) {
     return await this.menuRepo.findOne({
       where: { id },
-      relations: { children: true, user: true },
+      relations: { children: true, user: true, translations: true },
     });
   }
 

+ 82 - 90
packages/frontend/src/views/menu/list.vue

@@ -19,78 +19,62 @@
         </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="95" :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="description" :rule="{
-            required: false,
-            message: '请输入描述',
-            trigger: ['input', 'blur'],
-          }"
-        >
+        <!-- <n-form-item label="描述" path="description" :rule="{
+          required: false,
+          message: '请输入描述',
+          trigger: ['input', 'blur'],
+        }">
           <n-input v-model:value="modalForm.description" type="textarea" />
-        </n-form-item>
+        </n-form-item> -->
         <n-form-item v-if="modalForm.level !== 0" label="封面" path="cover">
-          <n-upload
-            :multiple="false" :default-upload="true" list-type="image-card" :custom-request="uploadCover"
-            :max="1" :default-file-list="previewFileList" @preview="handlePreview" @remove="handleCoverRemove"
-          />
+          <n-upload :multiple="false" :default-upload="true" list-type="image-card" :custom-request="uploadCover"
+            :max="1" :default-file-list="previewFileList" @preview="handlePreview" @remove="handleCoverRemove" />
           <n-modal v-model:show="showModal" preset="card" style="width: 600px" title="">
             <img :src="previewImageUrl" style="width: 100%">
           </n-modal>
         </n-form-item>
-        <n-form-item
-          label="分类" path="categoryId" :rule="{
-            required: modalForm.level === 0 ? false : true,
-            type: 'number',
-            trigger: ['change', 'blur'],
-            message: '请输入分类',
-          }"
-        >
-          <n-tree-select
-            v-model:value="modalForm.categoryId" :options="allCategory" label-field="title" key-field="id"
-            placeholder="根分类" clearable
-          />
+        <n-form-item label="分类" path="categoryId" :rule="{
+          required: modalForm.level === 0 ? false : true,
+          type: 'number',
+          trigger: ['change', 'blur'],
+          message: '请输入分类',
+        }">
+          <n-tree-select v-model:value="modalForm.categoryId" :options="allCategory" label-field="title" key-field="id"
+            placeholder="根分类" clearable />
         </n-form-item>
 
-        <n-form-item
-          v-if="modalForm.level === 0" label="样式类型" path="styleType" :rule="{
-            required: true,
-            type: 'number',
-            message: '请输入样式类型',
-            trigger: ['input', 'blur'],
-          }"
-        >
+        <n-form-item v-if="modalForm.level === 0" label="样式类型" path="styleType" :rule="{
+          required: true,
+          type: 'number',
+          message: '请输入样式类型',
+          trigger: ['input', 'blur'],
+        }">
           <n-select v-model:value="modalForm.styleType" :options="styleEnum" clearable filterable tag />
         </n-form-item>
 
-        <n-form-item
-          label="文章链接" path="articleId" :rule="{
-            required: false,
-            type: 'number',
-            trigger: ['change', 'blur'],
-            message: '请输入文章链接',
-          }"
-        >
+        <n-form-item label="文章链接" path="articleId" :rule="{
+          required: false,
+          type: 'number',
+          trigger: ['change', 'blur'],
+          message: '请输入文章链接',
+        }">
           <n-select v-model:value="modalForm.articleId" :options="allArticle" clearable filterable tag />
         </n-form-item>
 
@@ -98,37 +82,31 @@
           <n-input-number v-model:value="modalForm.grid" style="width:100%" />
         </n-form-item>
 
-        <n-form-item
-          label="排序" path="order" :rule="{
-            type: 'number',
-            required: true,
-            message: '此为必填项',
-            trigger: ['blur', 'change'],
-          }"
-        >
+        <n-form-item label="排序" path="order" :rule="{
+          type: 'number',
+          required: true,
+          message: '此为必填项',
+          trigger: ['blur', 'change'],
+        }">
           <n-input-number v-model:value="modalForm.order" />
         </n-form-item>
 
-        <n-tabs type="line" animated>
+        <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-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-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>
@@ -196,26 +174,35 @@ const queryItems = ref({
   parentId: route.params.id,
 })
 
-const modalForm = ref({})
+// const modalForm = ref({})
 
-const { modalRef, modalFormRef, modalAction, modalForm: newmodalForm, handleAdd, handleDelete, handleEdit }
+const { modalRef, modalFormRef, modalAction, modalForm, handleAdd, handleDelete, handleEdit }
   = useCrud({
     name: '子菜单',
     doCreate: api.create,
     doDelete: api.delete,
     doUpdate: api.update,
-    initForm: { enable: true, isPublish: true, order: 0 },
+    initForm: {
+      enable: true,
+      isPublish: true,
+      order: 0,
+      translations: [
+        { locale: 'zh', title: '', description: '' },
+        { locale: 'en', title: '', description: '' },
+      ],
+    },
     refresh: (_, keepCurrentPage) => $table.value?.handleSearch(keepCurrentPage),
   })
 
 watchEffect(() => {
-  if (newmodalForm.value) {
-    modalForm.value = {
-      ...newmodalForm.value,
-    }
-    initTranslations(modalForm.value, ['title', 'description'])
-    console.log('modalForm', modalForm)
-  }
+  // if (newmodalForm.value) {
+  //   modalForm.value = {
+  //     ...modalForm.value,
+  //     ...newmodalForm.value,
+  //   }
+  //   initTranslations(modalForm.value, ['title', 'description'])
+  //   console.log('modalForm', modalForm.value)
+  // }
 })
 async function getMenuDetail() {
   const { data } = await api.getOne(route.params.id)
@@ -234,10 +221,10 @@ const columns = [
     key: 'cover',
     render: row => row.cover
       ? h(NImage, {
-          src: row.cover,
-          height: 60,
-          width: 80,
-        })
+        src: row.cover,
+        height: 60,
+        width: 80,
+      })
       : null,
   },
   {
@@ -370,6 +357,11 @@ async function handleFormEdit(data = {}) {
   else {
     previewFileList.value = []
   }
+
+  if (data.translations.length === 0) {
+    initTranslations(data, ['title', 'description'])
+  }
+
   handleEdit(data)
 }