gemercheung 7 ヶ月 前
コミット
c6885d0911

+ 4 - 0
packages/backend/src/common/utils/index.ts

@@ -2,8 +2,12 @@ import * as dayjs from 'dayjs';
 import * as customParseFormat from 'dayjs/plugin/customParseFormat.js';
 import * as utc from 'dayjs/plugin/utc.js';
 import * as timezone from 'dayjs/plugin/timezone.js';
+
 dayjs.extend(customParseFormat);
 dayjs.extend(utc);
 dayjs.extend(timezone);
 
 export { dayjs };
+
+// export * from './getTopLevelMenuAndPath';
+export * from './treeUtil';

+ 43 - 0
packages/backend/src/common/utils/treeUtil.ts

@@ -0,0 +1,43 @@
+export interface TreeNode {
+  id: number;
+  parentId: number | null;
+  title: string;
+  children?: TreeNode[];
+}
+
+export function getTopMenuFragment(tree: TreeNode[], targetId: number): TreeNode | null {
+  // 辅助递归函数:查找目标节点并返回其顶层父菜单的完整树结构
+  function findTopMenu(node: TreeNode): TreeNode | null {
+    // 检查当前节点是否为目标节点
+    if (node.id === targetId) {
+      return node; // 找到目标节点,返回当前节点
+    }
+
+    // 如果当前节点有子节点,递归查找
+    if (node.children) {
+      for (let i = 0; i < node.children.length; i++) {
+        const child = node.children[i];
+        const result = findTopMenu(child); // 递归查找子节点
+
+        if (result) {
+          // 如果在子节点中找到目标节点,返回当前节点的完整树结构
+          return node;
+        }
+      }
+    }
+
+    // 如果当前节点及其子节点中都没有找到目标节点,返回 null
+    return null;
+  }
+
+  // 遍历顶层节点,查找目标节点的顶层父菜单
+  for (const root of tree) {
+    const result = findTopMenu(root);
+    if (result) {
+      return result; // 返回顶层父菜单的完整树结构
+    }
+  }
+
+  // 如果整个树中都没有找到目标节点,返回 null
+  return null;
+}

+ 10 - 4
packages/backend/src/modules/web/web.controller.ts

@@ -1,4 +1,4 @@
-import { Controller, Get, Post, Body, Patch, Param, Delete, Headers } from '@nestjs/common';
+import { Controller, Get, Post, Body, Patch, Param, Delete, Headers, Query } from '@nestjs/common';
 import { WebService } from './web.service';
 import { ApiTags } from '@nestjs/swagger';
 
@@ -22,8 +22,14 @@ export class WebController {
     return this.webService.setArticleCount(+id);
   }
 
-  @Get('category/:id')
-  getAllCategoriesList(@Param('id') id: string) {
-    return this.webService.getCategory(+id);
+  @Get('what')
+  getArticleWithSearch(@Query('key') key: string, @Headers('locale') locale?: string) {
+    return this.webService.searchArticles(key, locale);
+    // return true;
+  }
+
+  @Get('category/tree')
+  getCategoryTree(@Query('id') id: string) {
+    return this.webService.getCategoryTreeAndPath(+id);
   }
 }

+ 52 - 4
packages/backend/src/modules/web/web.service.ts

@@ -1,10 +1,11 @@
 import { BadRequestException, Injectable } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
-import { Like, Repository } from 'typeorm';
+import { In, Like, Repository } from 'typeorm';
 import { Menu } from '@/modules/menu/menu.entity';
 import { SharedService } from '@/shared/shared.service';
 import { Article } from '@/modules/article/article.entity';
 import { Category } from '@/modules/category/category.entity';
+import { getTopMenuFragment } from '@/common/utils';
 
 @Injectable()
 export class WebService {
@@ -73,11 +74,58 @@ export class WebService {
     return true;
   }
 
-  async getCategory(id: number) {
-    const category = await this.categoryRepo.findOne({
+  async getTopCategory1(id: number) {
+    let currentCategory = await this.categoryRepo.findOne({
       where: { id },
+      relations: { parent: true },
+    });
+
+    if (!currentCategory) {
+      return null; // 如果分类不存在,返回 null
+    }
+    // 递归查找顶层父分类
+    while (currentCategory.parent) {
+      currentCategory = await this.categoryRepo.findOne({
+        where: { id: currentCategory.parent.id },
+        relations: { parent: true },
+      });
+    }
+    console.log('currentCategory', currentCategory);
+    return currentCategory.id
+      ? this.categoryRepo.findOne({
+          where: { id: currentCategory.id },
+          relations: { children: true },
+        })
+      : null;
+  }
+
+  async getCategoryTreeAndPath(id: number) {
+    const categories = await this.categoryRepo.find({
       relations: { children: true },
     });
-    return category;
+    const data = this.sharedService.handleTree(categories);
+    return getTopMenuFragment(data, id);
+  }
+
+  async searchArticles(key: string, locale?: string) {
+    const lang = this.sharedService.handleValidLang(locale);
+    const articles = await this.articleRepo.find({
+      where: [
+        {
+          translations: {
+            locale: lang,
+            title: Like(`%${key || ''}%`),
+          },
+        },
+        {
+          translations: {
+            locale: lang,
+            content: Like(`%${key || ''}%`),
+          },
+        },
+      ],
+      relations: { translations: true },
+    });
+    return articles.map((article) => article.translate(lang));
   }
 }

+ 1 - 1
packages/frontend/src/views/article/add.vue

@@ -95,7 +95,7 @@ onMounted(() => {
   console.log('VividEditor', VividEditor)
 })
 const allCategory = ref([])
-categoryApi.getAll().then(({ data = [] }) => (allCategory.value = data.map(item => ({ label: item.title, value: item.id }))))
+categoryApi.getAll().then(({ data = [] }) => (allCategory.value = data))
 
 function handleAdd() {
   modalFormRef.value?.validate((errors) => {

+ 8 - 0
packages/web/src/api/menu.ts

@@ -10,4 +10,12 @@ export type MenuItem = {
   styleType: number
   description: string
 }
+export type CategoryItem = {
+  id: number
+  title: string
+  children: MenuItem[]
+  remark: string
+}
 export const getMenuList = (): Promise<ResultData<MenuItem[]>> => request.get('web/menu')
+export const getCategoryTree = (id: string): Promise<ResultData<CategoryItem[]>> =>
+  request.get('web/category/tree', { id })

+ 14 - 12
packages/web/src/pages/showdoc/[id].vue

@@ -4,15 +4,11 @@
       <div class="flex flex-row flex-nowrap">
         <div class="flex-1 flex-basis-[240px] flex-grow-0">
           <n-collapse accordion class="br-1px_#EBEBEB py-[10px]">
-            <n-collapse-item title="青铜" name="1">
-              <div class="px-4">可以</div>
-            </n-collapse-item>
-            <n-collapse-item title="白银" name="2">
-              <div class="px-4">很好</div>
-            </n-collapse-item>
-            <n-collapse-item title="黄金" name="3">
-              <div class="px-4">真棒</div>
-            </n-collapse-item>
+            <template v-for="(cate, index) in mainCategories.children" :key="index">
+              <n-collapse-item :title="cate.title" :name="cate.title">
+                <div class="px-4">{{ cate.title }}</div>
+              </n-collapse-item>
+            </template>
           </n-collapse>
         </div>
         <div class="flex-1 px-[40px] mb-[120px]">
@@ -62,8 +58,10 @@ layout: "base"
 import {
   type ArticleDetailType,
   type ArticleDetailMenuType,
+  type CategoryItem,
   getArticleDetail,
   getArticleCount,
+  getCategoryTree,
 } from '@/api'
 import { htmlToTree, createAnchorNames, dayjs } from '@/utils'
 import { NH1, NH4, NCollapse, NCollapseItem, NAnchor, NAnchorLink } from 'naive-ui'
@@ -75,23 +73,27 @@ console.log('route', route)
 const detail = ref<ArticleDetailType | undefined>()
 const mainContents = ref<ArticleDetailMenuType[]>([])
 
-onMounted(() => {
+const mainCategories = ref<CategoryItem[]>([])
+onMounted(async () => {
   setTimeout(() => {
     const html = document.querySelector('.content-html')
     if (html) {
       createAnchorNames(html)
     }
   }, 1000)
-  getArticleCount(+id)
+  await getArticleCount(+id)
 })
 
 watchEffect(() => {
   if (id) {
-    getArticleDetail(+id).then((data) => {
+    getArticleDetail(+id).then(async (data) => {
       if (data.data) {
         detail.value = data.data
         document.title = detail.value.title
         mainContents.value = htmlToTree(detail.value.content)
+        const res = await getCategoryTree(+detail.value.categoryId)
+        // console.log('data', res.data)
+        mainCategories.value = res.data
       }
     })
   }

+ 36 - 0
packages/web/src/utils/getTopParentId.ts

@@ -0,0 +1,36 @@
+interface TreeNode {
+  id: string;
+  name: string;
+  children?: TreeNode[];
+}
+export function getTopLevelMenuAndPath(tree: TreeNode[], targetId: string): TreeNode | null {
+  // 辅助函数:递归查找目标节点及其路径
+  function findPath(node: TreeNode, path: TreeNode[]): TreeNode | null {
+    // 将当前节点加入路径
+    path.push(node);
+
+    // 如果当前节点是目标节点,返回路径
+    if (node.id === targetId) {
+      return { ...node, children: path }; // 返回顶层菜单及其路径
+    }
+
+    // 如果有子节点,递归查找
+    if (node.children) {
+      for (const child of node.children) {
+        const result = findPath(child, path.slice()); // 使用 path.slice() 创建新路径副本
+        if (result) return result;
+      }
+    }
+
+    return null; // 如果当前分支没有找到目标节点,返回 null
+  }
+
+  // 遍历顶层节点,查找目标节点
+  for (const root of tree) {
+    const result = findPath(root, []);
+    if (result) return result;
+  }
+
+  return null; // 如果整个树中没有找到目标节点,返回 null
+}
+

+ 1 - 0
packages/web/src/utils/index.ts

@@ -1,3 +1,4 @@
 export * from './http'
 export * from './html'
 export * from './time'
+export * from './getTopParentId'