Browse Source

feat: save

gemercheung 7 tháng trước cách đây
mục cha
commit
7bb02aec5d

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

@@ -7,6 +7,7 @@ export type ArticleDetailType = {
   content: string
   createTime: string
   readCount: number
+  categoryId: number
 }
 
 export type ArticleDetailMenuType = {

+ 1 - 1
packages/web/src/api/menu.ts

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

+ 16 - 2
packages/web/src/components/header.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="header grid min-h-sm w-full  flex flex-col">
+  <div class="header grid min-h-sm w-full flex flex-col">
     <div class="w-full h-[100px] flex max-w-screen-xl my-0 mx-auto justify-between items-center">
       <div class="logo">
         <a href="/"> <img src="@/assets/img/logo_4dge_cn.png" alt="logo" /></a>
@@ -13,7 +13,14 @@
 
     <div class="search-box w-full flex justify-center items-center flex-col">
       <n-h1 class="font-size-[48px] font-500">{{ $t('helperTip') }}</n-h1>
-      <n-input class="max-w-[640px]" size="large" round placeholder="输入关键字">
+      <n-input
+        v-model:value="keySearch"
+        class="max-w-[640px]"
+        size="large"
+        round
+        placeholder="输入关键字"
+        @keydown.enter="handleSearch"
+      >
         <template #prefix>
           <n-icon :component="SearchOutline" />
         </template>
@@ -27,6 +34,8 @@ import { SearchOutline } from '@vicons/ionicons5'
 
 const { t, locale } = useI18n()
 
+const keySearch = ref('')
+const router = useRouter()
 const options = ref([
   {
     label: t('zh'),
@@ -41,6 +50,11 @@ const options = ref([
 const handleSelect = (key: string) => {
   locale.value = key
 }
+
+const handleSearch = () => {
+  console.log('keySearch', keySearch.value)
+  router.push({ path: '/search', query: { key: keySearch.value } })
+}
 </script>
 <style>
 .header {

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

@@ -0,0 +1,74 @@
+<template>
+  <div>
+    <!--    {{ list }}-->
+
+    <div class="max-w-screen-lg my-[50px] mx-auto">
+      <n-h2> 搜索结果:</n-h2>
+      <template v-if="list.length > 0">
+        <n-list hoverable clickable>
+          <template v-for="(item, index) in list" :key="index">
+            <n-list-item @click="handleToArticle(item)">
+              <n-thing :title="item.title" content-style="margin-top: 10px;">
+                <template #description>
+                  <div class="color-[#909090]">
+                    {{ htmlspecialchars(item.content) }}
+                  </div>
+                </template>
+              </n-thing>
+            </n-list-item>
+          </template>
+        </n-list>
+
+        <div class="my-[50px] mx-auto text-center color-[#909090]">共 {{ list.length }} 条数据</div>
+      </template>
+      <n-empty
+        v-else
+        size="large"
+        description="暂时没有数据"
+        class="min-h-[500px] flex align-center justify-center"
+      >
+        <template #extra>
+          <n-button size="small" @click="$router.push('/')"> 返回</n-button>
+        </template>
+      </n-empty>
+    </div>
+  </div>
+</template>
+
+<route>
+{
+name: "search",
+meta: {
+layout: "base"
+}
+}
+</route>
+<script lang="ts" setup>
+import { NList, NListItem, NThing, NH2, NEmpty, NButton } from 'naive-ui'
+import { type ArticleDetailType, getArticleSearch } from '@/api'
+import router from '@/plugins/router.ts'
+
+const list = ref<ArticleDetailType[]>([])
+
+function htmlspecialchars(str: string) {
+  const div = document.createElement('div')
+  div.innerHTML = str
+  const text = div.textContent || ''
+  return text.length > 150 ? `${text.substring(0, 150)}...` : text
+}
+
+const route = useRoute()
+onMounted(async () => {
+  watchEffect(() => {
+    if (route.query.key) {
+      getArticleSearch(route.query.key).then((result) => {
+        list.value = result.data
+      })
+    }
+  })
+})
+
+const handleToArticle = (item: ArticleDetailType) => {
+  router.push(`/showdoc/${item.id}`)
+}
+</script>

+ 26 - 13
packages/web/src/pages/showdoc/[id].vue

@@ -1,8 +1,8 @@
 <template>
-  <div class="max-w-screen-xl content my-0 mx-auto text-size-base">
+  <div class="max-w-screen-xl content my-0 mx-auto text-size-base overflow-hidden">
     <div v-if="detail" class="mt-[100px]">
-      <div class="flex flex-row flex-nowrap">
-        <div class="flex-1 flex-basis-[240px] flex-grow-0">
+      <div class="w-full flex flex-row flex-nowrap">
+        <div class="flex-basis-[240px] flex-grow-0 flex-shrink-0">
           <n-collapse accordion 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">
@@ -11,7 +11,7 @@
             </template>
           </n-collapse>
         </div>
-        <div class="flex-1 px-[40px] mb-[120px]">
+        <div class="flex-1 w-[calc(100%-80px)] px-[40px] mb-[120px] overflow-hidden">
           <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]">
@@ -23,7 +23,7 @@
           <div class="w-full content-html" v-html="detail.content"></div>
         </div>
 
-        <div class="flex-1 flex-basis-[240px] flex-grow-0">
+        <div class="flex-basis-[240px] flex-grow-0 flex-shrink-0">
           <div class="min-h-[200px] bg-[#F5F9FF] b-r-[8px] p-[20px]">
             <n-h4 class="font-600 ext-size-base">主要内容</n-h4>
             <n-anchor :show-rail="false">
@@ -67,7 +67,10 @@ import { htmlToTree, createAnchorNames, dayjs } from '@/utils'
 import { NH1, NH4, NCollapse, NCollapseItem, NAnchor, NAnchorLink } from 'naive-ui'
 
 const route = useRoute()
-const id = route.params?.id
+
+const params = route.params as {
+  id?: number
+}
 
 console.log('route', route)
 const detail = ref<ArticleDetailType | undefined>()
@@ -81,23 +84,33 @@ onMounted(async () => {
       createAnchorNames(html)
     }
   }, 1000)
-  await getArticleCount(+id)
+  if (params.id) {
+    await getArticleCount(+params.id)
+  }
 })
 
 watchEffect(() => {
-  if (id) {
-    getArticleDetail(+id).then(async (data) => {
+  if (params.id) {
+    getArticleDetail(+params.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
+        if (detail.value.categoryId) {
+          const res = await getCategoryTree(detail.value.categoryId)
+          if (res.data) {
+            mainCategories.value = res.data as CategoryItem[]
+          }
+        }
       }
     })
   }
 })
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+:deep(.content-html img) {
+  width: 100% !important;
+  height: auto;
+}
+</style>

+ 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>>,
+    'search': RouteRecordInfo<'search', '/search', Record<never, never>, Record<never, never>>,
     'showdoc': RouteRecordInfo<'showdoc', '/showdoc/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
   }
 }