Browse Source

feat: 上下篇

gemercheung 6 months ago
parent
commit
d31e050059

+ 2 - 0
packages/backend/src/common/exceptions/error-code.ts

@@ -30,6 +30,8 @@ export const ERR = {
   ERR_20003: { code: 20003, message: '已超出支持的最大处理数量' },
   ERR_20003: { code: 20003, message: '已超出支持的最大处理数量' },
   // 环境相关
   // 环境相关
   ERR_30001: { code: 30001, message: '预览环境不支持此操作' },
   ERR_30001: { code: 30001, message: '预览环境不支持此操作' },
+  //
+  ERR_4000: { code: 4000, message: '当前数据不存在' },
 } as const;
 } as const;
 
 
 export type ErrInfo = ValueOf<typeof ERR>;
 export type ErrInfo = ValueOf<typeof ERR>;

+ 6 - 1
packages/backend/src/modules/web/web.controller.ts

@@ -5,7 +5,7 @@ import { ApiTags } from '@nestjs/swagger';
 @ApiTags('website(展示端)')
 @ApiTags('website(展示端)')
 @Controller('web')
 @Controller('web')
 export class WebController {
 export class WebController {
-  constructor(private readonly webService: WebService) {}
+  constructor(private readonly webService: WebService) { }
 
 
   @Get('menu')
   @Get('menu')
   getMenus(@Headers('locale') locale?: string) {
   getMenus(@Headers('locale') locale?: string) {
@@ -22,6 +22,11 @@ export class WebController {
     return this.webService.setArticleCount(+id);
     return this.webService.setArticleCount(+id);
   }
   }
 
 
+  @Get('article/near/:id')
+  getNearArticle(@Param('id') id: string, @Headers('locale') locale?: string) {
+    return this.webService.getNearArticle(+id, locale);
+  }
+
   @Get('category/tree')
   @Get('category/tree')
   getCategoryTree(@Query('id') id: string) {
   getCategoryTree(@Query('id') id: string) {
     return this.webService.getCategoryTreeAndPath(+id);
     return this.webService.getCategoryTreeAndPath(+id);

+ 26 - 2
packages/backend/src/modules/web/web.service.ts

@@ -1,11 +1,12 @@
 import { BadRequestException, Injectable } from '@nestjs/common';
 import { BadRequestException, Injectable } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
 import { InjectRepository } from '@nestjs/typeorm';
-import { In, Like, Repository } from 'typeorm';
+import { In, LessThan, Like, MoreThan, Repository } from 'typeorm';
 import { Menu } from '@/modules/menu/menu.entity';
 import { Menu } from '@/modules/menu/menu.entity';
 import { SharedService } from '@/shared/shared.service';
 import { SharedService } from '@/shared/shared.service';
 import { Article } from '@/modules/article/article.entity';
 import { Article } from '@/modules/article/article.entity';
 import { Category } from '@/modules/category/category.entity';
 import { Category } from '@/modules/category/category.entity';
 import { getTopMenuFragment } from '@/common/utils';
 import { getTopMenuFragment } from '@/common/utils';
+import { CustomException, ErrorCode } from '@/common/exceptions/custom.exception';
 
 
 @Injectable()
 @Injectable()
 export class WebService {
 export class WebService {
@@ -17,7 +18,7 @@ export class WebService {
     @InjectRepository(Category)
     @InjectRepository(Category)
     private categoryRepo: Repository<Category>,
     private categoryRepo: Repository<Category>,
     private readonly sharedService: SharedService,
     private readonly sharedService: SharedService,
-  ) {}
+  ) { }
 
 
   async findMenuTree(locale?: string): Promise<Menu[]> {
   async findMenuTree(locale?: string): Promise<Menu[]> {
     const lang = this.sharedService.handleValidLang(locale);
     const lang = this.sharedService.handleValidLang(locale);
@@ -167,4 +168,27 @@ export class WebService {
     });
     });
     return articles.map((article) => article.translate(lang));
     return articles.map((article) => article.translate(lang));
   }
   }
+  async getNearArticle(currentId: number, locale?: string) {
+    const lang = this.sharedService.handleValidLang(locale);
+    const currentEntity = await this.articleRepo.findOne({ where: { id: currentId } });
+    if (!currentEntity) {
+      throw new CustomException(ErrorCode.ERR_4000, '当前数据不存在');
+    }
+
+    const prevEntity = await this.articleRepo.findOne({
+      where: { id: LessThan(currentId), categoryId: currentEntity.categoryId },
+      order: { id: 'DESC' },
+      relations: { translations: true },
+    });
+
+    const nextEntity = await this.articleRepo.findOne({
+      where: { id: MoreThan(currentId), categoryId: currentEntity.categoryId },
+      order: { id: 'ASC' },
+      relations: { translations: true },
+    });
+    return [
+      prevEntity ? prevEntity.translate(lang) : null,
+      nextEntity ? nextEntity.translate(lang) : null,
+    ];
+  }
 }
 }

+ 3 - 0
packages/web/src/api/article.ts

@@ -29,3 +29,6 @@ export const getArticleSearch = (keyword: string): Promise<ResultData<ArticleDet
 export const getArticlesByCateId = (cid: number): Promise<ResultData<ArticleDetailType[]>> =>
 export const getArticlesByCateId = (cid: number): Promise<ResultData<ArticleDetailType[]>> =>
   request.get(`web/articleWithCate/${cid}`)
   request.get(`web/articleWithCate/${cid}`)
 
 
+export const getNearArticles = (cid: number): Promise<ResultData<ArticleDetailType[]>> =>
+  request.get(`web/article/near/${cid}`)
+

+ 5 - 3
packages/web/src/locales/json/en.json

@@ -28,7 +28,9 @@
   "publish": "Publish At",
   "publish": "Publish At",
   "main_content": "Main Content",
   "main_content": "Main Content",
   "read": "Read",
   "read": "Read",
-   "more":"More",
-   "developer": "Developer",
-   "recommend_reading": "Recommend Reading"
+  "more": "More",
+  "developer": "Developer",
+  "recommend_reading": "Recommend Reading",
+  "prev_article": " < PREVIOUS",
+  "next_article": " NEXT >"
 }
 }

+ 3 - 1
packages/web/src/locales/json/zh.json

@@ -31,5 +31,7 @@
   "read": "阅读",
   "read": "阅读",
   "more": "更多",
   "more": "更多",
   "developer": "开发者",
   "developer": "开发者",
-  "recommend_reading": "推荐阅读"
+  "recommend_reading": "推荐阅读",
+  "prev_article": " < 上一篇",
+  "next_article": " 下一篇 >"
 }
 }

+ 58 - 22
packages/web/src/pages/showdoc/[id].vue

@@ -3,7 +3,7 @@
     <div v-if="detail">
     <div v-if="detail">
       <div class="breadcrumb my-[30px] pb-[20px] bb-1px_#EBEBEB" role="navigation">
       <div class="breadcrumb my-[30px] pb-[20px] bb-1px_#EBEBEB" role="navigation">
         <n-breadcrumb separator=">" v-if="breadcrumb">
         <n-breadcrumb separator=">" v-if="breadcrumb">
-          <!-- {{ breadcrumb }} -->
+          <!--          {{ breadcrumb }}-->
           <template v-for="(bread, index) in breadcrumb" :key="index">
           <template v-for="(bread, index) in breadcrumb" :key="index">
             <n-breadcrumb-item :clickable="false"> {{ bread.title }}</n-breadcrumb-item>
             <n-breadcrumb-item :clickable="false"> {{ bread.title }}</n-breadcrumb-item>
           </template>
           </template>
@@ -17,20 +17,7 @@
           <n-tree class="left-tree" v-if="mainCategories.children" block-line :data="mainCategories.children"
           <n-tree class="left-tree" v-if="mainCategories.children" block-line :data="mainCategories.children"
             :default-expanded-keys="defaultExpandedKeys" :node-props="nodeProps" key-field="id" label-field="title"
             :default-expanded-keys="defaultExpandedKeys" :node-props="nodeProps" key-field="id" label-field="title"
             children-field="children" selectable />
             children-field="children" selectable />
-          <!--          <n-collapse class="br-1px_#EBEBEB py-[10px]">-->
-          <!--            <template v-for="(cate, index) in mainCategories.children" :key="index">-->
-          <!--              <n-collapse-item :title="cate.title" :name="cate.title">-->
-          <!--                <n-collapse v-if="cate.children">-->
-          <!--                  <template v-for="(cateChild, cIndex) in cate.children" :key="cIndex">-->
-          <!--                    <n-collapse-item :title="cateChild.title" :name="cateChild.title">-->
-          <!--                      <div class="px-6">{{ cateChild.title }}</div>-->
-          <!--                    </n-collapse-item>-->
-          <!--                  </template>-->
-          <!--                </n-collapse>-->
-          <!--                <div class="px-6" v-else>{{ cate.title }}</div>-->
-          <!--              </n-collapse-item>-->
-          <!--            </template>-->
-          <!--          </n-collapse>-->
+
         </div>
         </div>
         <div class="flex-1 w-[calc(100%-80px)] px-[40px] mb-[120px] overflow-hidden">
         <div class="flex-1 w-[calc(100%-80px)] px-[40px] mb-[120px] overflow-hidden">
           <div class="bb-1px_#EBEBEB color-[#999999]">
           <div class="bb-1px_#EBEBEB color-[#999999]">
@@ -38,10 +25,36 @@
             <span class="flex flex-row gap-col-6 pb-[15px]">
             <span class="flex flex-row gap-col-6 pb-[15px]">
               <p>{{ $t('publish') }} {{ dayjs(detail.createTime).format('YYYY-MM-DD') }}</p>
               <p>{{ $t('publish') }} {{ dayjs(detail.createTime).format('YYYY-MM-DD') }}</p>
               <p>{{ $t('read') }} ( {{ detail.readCount }} )</p>
               <p>{{ $t('read') }} ( {{ detail.readCount }} )</p>
+
             </span>
             </span>
           </div>
           </div>
 
 
           <div class="w-full content-html" v-html="detail.content"></div>
           <div class="w-full content-html" v-html="detail.content"></div>
+          <div class="w-full flex justify-between pt-6 bt-1px_#EBEBEB color-[#999898]">
+            <div class="prev flex flex-col">
+              <span class="flex-1 text-align-left">
+                {{ $t('prev_article') }} </span>
+              <span class="flex-1 articleNear" v-if="articleNear[0]" @click="handleToArticle(articleNear[0])">
+                {{ articleNear[0].title }}
+              </span>
+              <span v-else>
+                暂没数据
+              </span>
+
+            </div>
+            <div class="next flex flex-col ">
+              <span class="flex-1 text-align-right">
+                {{ $t('next_article') }}
+              </span>
+              <span class="flex-1 articleNear" v-if="articleNear[1]" @click="handleToArticle(articleNear[1])">
+                {{ articleNear[1].title }}
+              </span>
+              <span v-else>
+                暂没数据
+              </span>
+            </div>
+          </div>
+
         </div>
         </div>
 
 
         <div class="flex-basis-[240px] flex-grow-0 flex-shrink-0">
         <div class="flex-basis-[240px] flex-grow-0 flex-shrink-0">
@@ -77,8 +90,9 @@ import {
   getArticleCount,
   getArticleCount,
   getCategoryTree,
   getCategoryTree,
   getArticlesByCateId,
   getArticlesByCateId,
+  getNearArticles,
 } from '@/api'
 } from '@/api'
-import { htmlToTree, createAnchorNames, dayjs, findNodeById, findBreadcrumbPath, type TreeNode } from '@/utils'
+import { htmlToTree, createAnchorNames, dayjs, findNodeById, findBreadcrumbPath } from '@/utils'
 import { NH1, NH2, NH4, NBreadcrumb, NBreadcrumbItem, NTree, NAnchor, NAnchorLink } from 'naive-ui'
 import { NH1, NH2, NH4, NBreadcrumb, NBreadcrumbItem, NTree, NAnchor, NAnchorLink } from 'naive-ui'
 import type { TreeOption } from 'naive-ui'
 import type { TreeOption } from 'naive-ui'
 
 
@@ -95,6 +109,7 @@ const mainContents = ref<ArticleDetailMenuType[]>([])
 const currentCate = ref<CategoryItem | undefined>()
 const currentCate = ref<CategoryItem | undefined>()
 const mainCategories = ref<CategoryItem[]>([])
 const mainCategories = ref<CategoryItem[]>([])
 const defaultExpandedKeys = ref<number[]>([])
 const defaultExpandedKeys = ref<number[]>([])
+const articleNear = ref<ArticleDetailType[]>([])
 
 
 const nodeProps = ({ option }: { option: TreeOption }) => {
 const nodeProps = ({ option }: { option: TreeOption }) => {
   return {
   return {
@@ -137,25 +152,39 @@ watchEffect(() => {
         document.title = detail.value.title
         document.title = detail.value.title
         mainContents.value = htmlToTree(detail.value.content)
         mainContents.value = htmlToTree(detail.value.content)
         if (detail.value.categoryId) {
         if (detail.value.categoryId) {
+
           const res = await getCategoryTree(detail.value.categoryId)
           const res = await getCategoryTree(detail.value.categoryId)
           if (res.data) {
           if (res.data) {
             mainCategories.value = res.data as CategoryItem[]
             mainCategories.value = res.data as CategoryItem[]
             if (mainCategories.value) {
             if (mainCategories.value) {
-              debugger
               currentCate.value = findNodeById(
               currentCate.value = findNodeById(
-                mainCategories.value as unknown as TreeNode[] || [],
-                String(detail.value.categoryId),
+                [mainCategories.value],
+                detail.value.categoryId,
               ) as unknown as CategoryItem
               ) as unknown as CategoryItem
               defaultExpandedKeys.value = [currentCate.value.parentId]
               defaultExpandedKeys.value = [currentCate.value.parentId]
-              breadcrumb.value = findBreadcrumbPath(mainCategories.value as unknown as TreeNode[] || [], String(detail.value.categoryId))
-              console.log('breadcrumb', breadcrumb.value)
+              breadcrumb.value = findBreadcrumbPath([mainCategories.value], detail.value.categoryId)
             }
             }
           }
           }
         }
         }
       }
       }
     })
     })
+    getNearArticles(+params.id).then(res => {
+      if (res.data) {
+        articleNear.value = res.data
+      }
+    })
+
   }
   }
 })
 })
+
+const handleToArticle = (article: ArticleDetailType) => {
+  console.log('article', article)
+  router.replace(`/showdoc/${article.id}`)
+  setTimeout(() => {
+    location.reload()
+  }, 500)
+}
+
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
@@ -165,7 +194,7 @@ watchEffect(() => {
 }
 }
 
 
 :deep(.left-tree) {
 :deep(.left-tree) {
-  //--n-node-content-height: 40px !important;
+  --n-node-content-height: 40px !important;
   //--n-node-color-hover: rgba(6,97,201,0.06) !important;
   //--n-node-color-hover: rgba(6,97,201,0.06) !important;
   //border-right: 1px solid #e5e7eb;
   //border-right: 1px solid #e5e7eb;
 
 
@@ -174,4 +203,11 @@ watchEffect(() => {
     font-size: 16px;
     font-size: 16px;
   }
   }
 }
 }
+
+.articleNear {
+  &:hover {
+    cursor: pointer;
+    color: #5a5a5a;
+  }
+}
 </style>
 </style>