瀏覽代碼

feat: save

gemercheung 7 月之前
父節點
當前提交
53238b246e

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

@@ -37,6 +37,9 @@ export class Article extends TranslatableEntity<ArticleTranslation> {
   @Column({ default: '' })
   remark: string;
 
+  @Column({ default: 0 })
+  readCount: number;
+
   @OneToOne(() => Category, {
     cascade: true,
     onDelete: 'CASCADE',

+ 5 - 0
packages/backend/src/modules/web/web.controller.ts

@@ -16,4 +16,9 @@ export class WebController {
   getArticleDetail(@Param('id') id: string, @Headers('locale') locale?: string) {
     return this.webService.findArticleDetail(+id, locale);
   }
+
+  @Get('article/count/:id')
+  getArticleCount(@Param('id') id: string) {
+    return this.webService.setArticleCount(+id);
+  }
 }

+ 12 - 1
packages/backend/src/modules/web/web.service.ts

@@ -1,4 +1,4 @@
-import { Injectable } from '@nestjs/common';
+import { BadRequestException, Injectable } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
 import { Like, Repository } from 'typeorm';
 import { Menu } from '@/modules/menu/menu.entity';
@@ -58,4 +58,15 @@ export class WebService {
     });
     return article.translate(lang);
   }
+
+  async setArticleCount(id: number) {
+    const article = await this.articleRepo.findOne({
+      where: { id },
+    });
+    if (!article) throw new BadRequestException('不存在或者已删除');
+    article.readCount = article.readCount + 1;
+    // console.log('article', article);
+    await this.articleRepo.save(article);
+    return true;
+  }
 }

+ 1 - 0
packages/web/package.json

@@ -27,6 +27,7 @@
     "@unocss/vite": "^65.4.0",
     "@vicons/ionicons5": "^0.13.0",
     "axios": "^1.7.9",
+    "dayjs": "^1.11.13",
     "naive-ui": "^2.40.3",
     "swiper": "^11.2.1",
     "unocss": "^0.65.1",

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

@@ -5,6 +5,8 @@ export type ArticleDetailType = {
   id: number
   title: string
   content: string
+  createTime: string
+  readCount: number
 }
 
 export type ArticleDetailMenuType = {
@@ -15,3 +17,6 @@ export type ArticleDetailMenuType = {
 
 export const getArticleDetail = (id: number): Promise<ResultData<ArticleDetailType>> =>
   request.get(`web/article/${id}`)
+
+export const getArticleCount = (id: number): Promise<ResultData<boolean>> =>
+  request.get(`web/article/count/${id}`)

+ 1 - 1
packages/web/src/pages/index.vue

@@ -153,7 +153,7 @@ const handleToDoc = (child: never) => {
   if (articleId) {
     router.push({ path: `/showdoc/${articleId}` })
   } else {
-    router.push({ path: '/showdoc', query: { cid: categoryId } })
+    // router.push({ path: '/showdoc', query: { cid: categoryId } })
   }
 }
 </script>

+ 0 - 0
packages/web/src/pages/search.vue


+ 18 - 5
packages/web/src/pages/showdoc/[id].vue

@@ -3,7 +3,7 @@
     <div v-if="detail" class="mt-[100px]">
       <div class="flex flex-row flex-nowrap">
         <div class="flex-1 flex-basis-[240px] flex-grow-0">
-          <n-collapse accordion class="">
+          <n-collapse accordion class="br-1px_#EBEBEB py-[10px]">
             <n-collapse-item title="青铜" name="1">
               <div class="px-4">可以</div>
             </n-collapse-item>
@@ -16,7 +16,14 @@
           </n-collapse>
         </div>
         <div class="flex-1 px-[40px] mb-[120px]">
-          <n-h1 class="font-700"> {{ detail.title }}</n-h1>
+          <div class="bb-1px_#EBEBEB color-[#999999]">
+            <n-h1 class="font-700"> {{ detail.title }}</n-h1>
+            <span class="flex flex-row gap-col-6 pb-[15px]">
+              <p>发布于{{ dayjs(detail.createTime).format('YYYY-MM-DD') }}</p>
+              <p>阅读 ( {{ detail.readCount }} )</p>
+            </span>
+          </div>
+
           <div class="w-full content-html" v-html="detail.content"></div>
         </div>
 
@@ -25,7 +32,7 @@
             <n-h4 class="font-600 ext-size-base">主要内容</n-h4>
             <n-anchor :show-rail="false">
               <n-anchor-link
-                class="text-size-base"
+                class="text-size-base color-[#999999]"
                 v-for="(item, index) in mainContents"
                 :key="index"
                 :title="item.text"
@@ -52,8 +59,13 @@ layout: "base"
 </route>
 
 <script setup lang="ts">
-import { type ArticleDetailType, type ArticleDetailMenuType, getArticleDetail } from '@/api'
-import { htmlToTree, createAnchorNames } from '@/utils'
+import {
+  type ArticleDetailType,
+  type ArticleDetailMenuType,
+  getArticleDetail,
+  getArticleCount,
+} from '@/api'
+import { htmlToTree, createAnchorNames, dayjs } from '@/utils'
 import { NH1, NH4, NCollapse, NCollapseItem, NAnchor, NAnchorLink } from 'naive-ui'
 
 const route = useRoute()
@@ -70,6 +82,7 @@ onMounted(() => {
       createAnchorNames(html)
     }
   }, 1000)
+  getArticleCount(+id)
 })
 
 watchEffect(() => {

+ 0 - 66
packages/web/src/pages/showdoc111.vue

@@ -1,66 +0,0 @@
-<template>
-  <div class="max-w-screen-xl content my-0 mx-auto text-size-base">
-    <div v-if="detail" class="mt-[100px]">
-      <div class="flex flex-row flex-nowrap">
-        <div class="flex-1 flex-basis-[240px] flex-grow-0">
-          <n-collapse accordion class="">
-            <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>
-          </n-collapse>
-        </div>
-        <div class="flex-1 px-[40px] mb-[120px]">
-          <n-h1 class="font-700"> {{ detail.title }}</n-h1>
-          <div class="w-full" v-html="detail.content"></div>
-        </div>
-
-        <div class="flex-1 flex-basis-[240px] flex-grow-0">
-          <div class="min-h-[200px] bg-[#F5F9FF] b-r-[8px] p-[20px]">
-            <n-h4 class="font-600">主要内容</n-h4>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<route>
-{
-name: "showdoc",
-  meta: {
-  layout: "base"
-  }
-}
-</route>
-
-<script setup lang="ts">
-import { type ArticleDetailType, getArticleDetail } from '@/api'
-import { NH1, NH4, NCollapse, NCollapseItem } from 'naive-ui'
-
-const route = useRoute()
-const id = route.query.id
-
-console.log('route', route)
-
-const detail = ref<ArticleDetailType | undefined>()
-
-watchEffect(() => {
-  if (id) {
-    getArticleDetail(+id).then((data) => {
-      if (data.data) {
-        console.log('ArticleDetail', data)
-        detail.value = data.data
-        document.title = detail.value.title
-      }
-    })
-  }
-})
-</script>
-
-<style lang="scss" scoped></style>

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

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

+ 11 - 0
packages/web/src/utils/time.ts

@@ -0,0 +1,11 @@
+import dayjs from 'dayjs'
+import customParseFormat from 'dayjs/plugin/customParseFormat.js'
+import utc from 'dayjs/plugin/utc.js'
+import timezone from 'dayjs/plugin/timezone.js'
+
+dayjs.extend(customParseFormat)
+dayjs.extend(utc)
+dayjs.extend(timezone)
+
+export { dayjs }
+

+ 1 - 1
packages/web/typed-router.d.ts

@@ -20,7 +20,7 @@ declare module 'vue-router/auto-routes' {
   export interface RouteNamedMap {
     'index': RouteRecordInfo<'index', '/', Record<never, never>, Record<never, never>>,
     '/about': RouteRecordInfo<'/about', '/about', Record<never, never>, Record<never, never>>,
+    '/search': RouteRecordInfo<'/search', '/search', Record<never, never>, Record<never, never>>,
     'showdoc': RouteRecordInfo<'showdoc', '/showdoc/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
-    'showdoc': RouteRecordInfo<'showdoc', '/showdoc111', Record<never, never>, Record<never, never>>,
   }
 }

+ 32 - 5
packages/web/unocss.config.js

@@ -1,5 +1,32 @@
-import { defineConfig } from 'unocss'
-
-export default defineConfig({
-  // 这里可以添加自定义配置
-})
+import { defineConfig, presetUno } from 'unocss'
+
+const DIRECTION_MAPPIINGS = { t: 'top', r: 'right', b: 'bottom', l: 'left' }
+
+export default defineConfig({
+  presets: [presetUno],
+  rules: [
+    [
+      /^b(t|r|b|l|d)-(.*)/,
+      ([, d, c]) => {
+        const direction = DIRECTION_MAPPIINGS[d] || ''
+        const p = direction ? `border-${direction}` : 'border'
+        const attrs = c.split('_')
+        if (
+          // 属性中不包含 border-style 则默认 solid
+          !attrs.some((item) =>
+            /^(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)$/.test(item),
+          )
+        ) {
+          attrs.push('solid')
+        }
+        // 属性中不包含 border-width 则默认 1px
+        if (!attrs.some((item) => /^\d/.test(item))) {
+          attrs.push('1px')
+        }
+        return {
+          [p]: attrs.join(' '),
+        }
+      },
+    ],
+  ],
+})

+ 3 - 0
pnpm-lock.yaml

@@ -538,6 +538,9 @@ importers:
       axios:
         specifier: ^1.7.9
         version: 1.7.9(debug@4.4.0)
+      dayjs:
+        specifier: ^1.11.13
+        version: 1.11.13
       naive-ui:
         specifier: ^2.40.3
         version: 2.40.4(vue@3.5.13(typescript@5.7.3))