Pārlūkot izejas kodu

feat: 新增移动端兼容

jinx 4 mēneši atpakaļ
vecāks
revīzija
0b09976b25

+ 2 - 1
packages/web/.env.development

@@ -9,4 +9,5 @@ VITE_AXIOS_BASE_URL = '/api'  # 用于代理
 # VITE_AXIOS_BASE_URL = 'https://mock.apipark.cn/m1/3776410-0-default'  # apifox云端mock
 
 # 代理配置-target
-VITE_PROXY_TARGET = 'http://localhost:8085'
+# VITE_PROXY_TARGET = 'http://localhost:8085'
+VITE_PROXY_TARGET = 'https://support.4dkankan.com'

+ 1 - 0
packages/web/components.d.ts

@@ -2,6 +2,7 @@
 // @ts-nocheck
 // Generated by unplugin-vue-components
 // Read more: https://github.com/vuejs/core/pull/3399
+// biome-ignore lint: disable
 export {}
 
 /* prettier-ignore */

+ 2 - 0
packages/web/package.json

@@ -27,6 +27,7 @@
     "@vueuse/core": "^12.5.0",
     "axios": "^1.7.9",
     "dayjs": "^1.11.13",
+    "lib-flexible": "^0.3.2",
     "naive-ui": "^2.40.3",
     "swiper": "^11.2.1",
     "unocss": "^0.65.1",
@@ -49,6 +50,7 @@
     "jsdom": "^25.0.1",
     "npm-run-all2": "^7.0.2",
     "prettier": "^3.4.2",
+    "sass-embedded": "^1.86.0",
     "start-server-and-test": "^2.0.9",
     "typescript": "~5.7.2",
     "unplugin-auto-import": "^19.0.0",

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 0
packages/web/public/static/js/flexible.js


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

@@ -12,6 +12,7 @@ export type ArticleDetailType = {
   children?: ArticleDetailType[]
 }
 
+
 export type ArticleDetailMenuType = {
   level: number
   text: string
@@ -32,4 +33,3 @@ export const getArticlesByCateId = (cid: number): Promise<ResultData<ArticleDeta
 
 export const getNearArticles = (cid: number): Promise<ResultData<ArticleDetailType[]>> =>
   request.get(`web/article/near/${cid}`)
-

BIN
packages/web/src/assets/img/mobile/banner_bg1.png


+ 273 - 30
packages/web/src/components/footer.vue

@@ -3,28 +3,108 @@
     <div class="w-full h-full flex flex-col justify-between max-w-screen-xl my-0 mx-auto">
       <div class="top w-full h-full mt-[60px] flex flex-row">
         <div class="left flex flex-col w-auto pr-[160px]">
-          <img v-if="locale === 'zh'" src="@/assets/img/logo.png" alt="logo" class="h-[43px] w-[160px] mb-5" />
-          <img v-if="locale === 'en'" src="@/assets/img/swsd-en.png" alt="logo" class="h-[43px] w-[160px] mb-5" />
-          <span class="font-size-[16px]"> {{ $t('contact') }}</span>
-          <span class="font-size-[24px] my-[10px]"> 400-669-8025</span>
+          <img
+            v-if="locale === 'zh'"
+            src="@/assets/img/logo.png"
+            alt="logo"
+            class="logo-icon h-[43px] w-[160px] mb-5"
+          />
+          <img
+            v-if="locale === 'en'"
+            src="@/assets/img/swsd-en.png"
+            alt="logo"
+            class="logo-icon h-[43px] w-[160px] mb-5"
+          />
+          <template v-if="!browser.isMobile()">
+            <span class="font-size-[16px]"> {{ $t('contact') }}</span>
+            <span class="font-size-[24px] my-[10px]"> 400-669-8025</span>
+          </template>
+          <template v-else>
+            <span class="font-size-[16px] my-1"> {{ $t('contact') }}:400-669-8025</span>
+          </template>
           <span class="font-size-[16px] my-1"> {{ $t('sell_cor') }}:sales@4dage.com</span>
           <span class="font-size-[16px] my-1"> {{ $t('pub_r') }}:pr@4dage.com</span>
         </div>
         <div class="flex-1 middle font-size-[16px]">
           <div class="infos-list flex flex-row gap-col-[80px]">
-            <div class="infos-item flex flex-col gap-row-[10px]">
-              <n-h6 class="text-white">{{ $t('products') }}</n-h6>
-              <a target="_blank" href="https://4dkankan.com/#/Meta">{{ $t('4DKanKan_Meta') }}</a>
-              <a target="_blank" href="https://4dkankan.com/#/Mega">{{ $t('4DKanKan_Mega') }}</a>
-              <a target="_blank" href="https://4dkankan.com/#/Minion">{{ $t('4DKanKan_Minion') }}</a>
-              <a target="_blank" href="https://4dkankan.com/#/KanKan">{{ $t('4DKanKan_Pro') }}</a>
+            <div class="infos-item flex flex-col gap-row-[10px]" :class="{ active: openId === 1 }">
+              <n-h6 class="line text-white" @click="toggleInfo(1)"
+                >{{ $t('products') }}
+
+                <n-icon class="arrow-icon">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    xmlns:xlink="http://www.w3.org/1999/xlink"
+                    viewBox="0 0 512 512"
+                  >
+                    <path
+                      fill="none"
+                      color="#909090"
+                      stroke="currentColor"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                      stroke-width="48"
+                      d="M184 112l144 144l-144 144"
+                    ></path>
+                  </svg> </n-icon
+              ></n-h6>
+              <a class="item-a" target="_blank" href="https://4dkankan.com/#/Meta">{{
+                $t('4DKanKan_Meta')
+              }}</a>
+              <a class="item-a" target="_blank" href="https://4dkankan.com/#/Mega">{{
+                $t('4DKanKan_Mega')
+              }}</a>
+              <a class="item-a" target="_blank" href="https://4dkankan.com/#/Minion">{{
+                $t('4DKanKan_Minion')
+              }}</a>
+              <a class="item-a" target="_blank" href="https://4dkankan.com/#/KanKan">{{
+                $t('4DKanKan_Pro')
+              }}</a>
             </div>
-            <div class="infos-item flex flex-col gap-row-[10px]">
-              <n-h6 class="text-white">{{ $t('solutions') }}</n-h6>
-              <a target="_blank" href="https://www.4dkankan.com/#/solutions/smartCity">{{ $t('smart_city') }}</a>
-              <a target="_blank" href="https://www.4dkankan.com/#/solutions/museum">{{ $t('museums') }}</a>
-              <a target="_blank" href="https://www.4dkankan.com/#/solutions/government">{{ $t('fire_sec') }}</a>
-              <a target="_blank" href="https://www.4dkankan.com/#/solutions/property">{{ $t('real_estate') }}</a>
+            <div class="infos-item flex flex-col gap-row-[10px]" :class="{ active: openId === 2 }">
+              <n-h6 class="line text-white" @click="toggleInfo(2)"
+                >{{ $t('solutions') }}
+                <n-icon class="arrow-icon">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    xmlns:xlink="http://www.w3.org/1999/xlink"
+                    viewBox="0 0 512 512"
+                  >
+                    <path
+                      fill="none"
+                      color="#909090"
+                      stroke="currentColor"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                      stroke-width="48"
+                      d="M184 112l144 144l-144 144"
+                    ></path>
+                  </svg> </n-icon
+              ></n-h6>
+              <a
+                class="item-a"
+                target="_blank"
+                href="https://www.4dkankan.com/#/solutions/smartCity"
+                >{{ $t('smart_city') }}</a
+              >
+              <a
+                class="item-a"
+                target="_blank"
+                href="https://www.4dkankan.com/#/solutions/museum"
+                >{{ $t('museums') }}</a
+              >
+              <a
+                class="item-a"
+                target="_blank"
+                href="https://www.4dkankan.com/#/solutions/government"
+                >{{ $t('fire_sec') }}</a
+              >
+              <a
+                class="item-a"
+                target="_blank"
+                href="https://www.4dkankan.com/#/solutions/property"
+                >{{ $t('real_estate') }}</a
+              >
             </div>
             <!-- <div class="infos-item flex flex-col gap-row-[10px]">
               <n-h6 class="text-white">{{ $t('support') }}</n-h6>
@@ -32,11 +112,35 @@
               <a target="_blank">{{ $t('help_center') }}</a>
               <a target="_blank" href="https://www.4dkankan.com/#/service/clause/pro">{{ $t('post-sale') }}</a>
             </div> -->
-            <div class="infos-item flex flex-col gap-row-[10px]">
-              <n-h6 class="text-white">{{ $t('about_us') }}</n-h6>
-              <a target="_blank" href="https://www.4dkankan.com/#/about">{{ $t('company_profile') }}</a>
-              <a target="_blank" href="https://www.4dkankan.com/#/news">{{ $t('news_report') }}</a><a target="_blank"
-                href="https://www.4dkankan.com/#/distributor">{{ $t('distributor') }}</a>
+            <div class="infos-item flex flex-col gap-row-[10px]" :class="{ active: openId === 3 }">
+              <n-h6 class="line text-white" @click="toggleInfo(3)"
+                >{{ $t('about_us') }}
+                <n-icon class="arrow-icon">
+                  <svg
+                    xmlns="http://www.w3.org/2000/svg"
+                    xmlns:xlink="http://www.w3.org/1999/xlink"
+                    viewBox="0 0 512 512"
+                  >
+                    <path
+                      fill="none"
+                      color="#909090"
+                      stroke="currentColor"
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                      stroke-width="48"
+                      d="M184 112l144 144l-144 144"
+                    ></path>
+                  </svg> </n-icon
+              ></n-h6>
+              <a class="item-a" target="_blank" href="https://www.4dkankan.com/#/about">{{
+                $t('company_profile')
+              }}</a>
+              <a class="item-a" target="_blank" href="https://www.4dkankan.com/#/news">{{
+                $t('news_report')
+              }}</a
+              ><a class="item-a" target="_blank" href="https://www.4dkankan.com/#/distributor">{{
+                $t('distributor')
+              }}</a>
             </div>
           </div>
         </div>
@@ -63,14 +167,27 @@
       <div class="bottom h-[82px] flex flex-row flex items-center justify-between">
         <div class="label">Copyright © 2022 4DAGE Co., Ltd. All rights reserved.</div>
         <div class="label icp">
-          <a target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=44049102496647"
-            class="text-white min-h-[20px] inline-flex items-center justify-center mr-3 decoration-none">
-            <img src="https://4dscene.4dage.com/new4dkk/v2/images/src/assets/images/baicon.d0289dc.png" alt="" />
+          <a
+            target="_blank"
+            href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=44049102496647"
+            class="icp-item text-white min-h-[20px] inline-flex items-center justify-center mr-3 decoration-none"
+          >
+            <img
+              class="icp-icon"
+              src="https://4dscene.4dage.com/new4dkk/v2/images/src/assets/images/baicon.d0289dc.png"
+              alt=""
+            />
             粤公网安备 44049102496647号
           </a>
-          <a target="_blank" href="https://beian.miit.gov.cn/"
-            class="text-white min-h-[20px] inline-flex items-center justify-center mr-3 decoration-none"><img
-              src="https://4dscene.4dage.com/new4dkk/v2/images/src/assets/images/baicon.d0289dc.png" alt="" />
+          <a
+            target="_blank"
+            href="https://beian.miit.gov.cn/"
+            class="icp-item text-white min-h-[20px] inline-flex items-center justify-center mr-3 decoration-none"
+            ><img
+              class="icp-icon"
+              src="https://4dscene.4dage.com/new4dkk/v2/images/src/assets/images/baicon.d0289dc.png"
+              alt=""
+            />
             粤ICP备14078495号
           </a>
         </div>
@@ -79,9 +196,22 @@
   </div>
 </template>
 <script setup lang="ts">
+import browser from '@/utils/browser'
+import { ChevronForward } from '@vicons/ionicons5'
 import { NH6 } from 'naive-ui'
 const { locale } = useI18n()
 
+const openId = ref<Number>(0)
+const toggleInfo = (type: Number) => {
+  if (!browser.isMobile()) {
+    return
+  }
+  if (openId.value == type) {
+    openId.value = 0
+    return
+  }
+  openId.value = type
+}
 </script>
 
 <style scoped>
@@ -97,11 +227,17 @@ a:hover {
 
 .footer {
   background: linear-gradient(180deg, #0661c9 0%, #033063 100%);
-  font-family: PingFang SC, PingFang SC;
+  font-family:
+    PingFang SC,
+    PingFang SC;
   font-weight: 400;
+  .line {
+    .arrow-icon {
+      display: none;
+    }
+  }
 }
 
-
 .bottom {
   border-top: 1px solid rgba(255, 255, 255, 0.1);
 }
@@ -109,10 +245,117 @@ a:hover {
 <style>
 .en .left {
   padding-right: 60px;
-
 }
 
 .en .infos-list {
   column-gap: 50px;
 }
+[is-mobile] {
+  .footer {
+    padding: 0 0.5333rem;
+    box-sizing: border-box;
+    min-height: auto;
+  }
+  .top,
+  .infos-list {
+    flex-direction: column;
+  }
+  .top {
+    .left {
+      padding: 0;
+      .my-1 {
+        font-weight: 400;
+        font-size: 0.32rem;
+        color: #ebebeb;
+        line-height: 0.3733rem;
+      }
+      .logo-icon {
+        width: 2.5867rem;
+        height: 0.6933rem;
+      }
+    }
+  }
+  .infos-list {
+    margin-bottom: 1.0667rem;
+    .infos-item {
+      .item-a {
+        display: none;
+        font-weight: 400;
+        font-size: 0.32rem;
+        color: #ebebeb;
+        line-height: 0.3733rem;
+      }
+      &.active {
+        .item-a {
+          display: block;
+        }
+        .line {
+          .arrow-icon {
+            transform: translateY(-50%) rotate(90deg);
+          }
+        }
+      }
+
+      .line {
+        height: 1.44rem;
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+        margin-bottom: 0;
+        border-bottom: 2px solid rgba(255, 255, 255, 0.1);
+        font-weight: 400;
+        font-size: 0.3733rem;
+        color: #ffffff;
+        line-height: 0.44rem;
+        position: relative;
+        .arrow-icon {
+          width: 0.32rem;
+          height: 0.1733rem;
+          position: absolute;
+          top: 50%;
+          right: 0;
+          transform: translateY(-50%) rotate(0deg);
+          display: block;
+        }
+      }
+    }
+  }
+  .bottom {
+    border: none;
+    flex-direction: column;
+    justify-content: flex-start;
+    height: auto;
+    .label {
+      font-weight: 400;
+      font-size: 0.32rem;
+      color: #ffffff;
+      line-height: 0.3733rem;
+      width: 100%;
+    }
+    .icp {
+      margin-top: 0.2667rem;
+      font-weight: 400;
+      font-size: 0.32rem;
+      color: #ffffff;
+      line-height: 0.3733rem;
+      margin-bottom: 0.2667rem;
+      align-items: flex-start;
+      flex-direction: column;
+      justify-content: flex-start;
+      width: 100%;
+      .icp-item {
+        width: 100%;
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+
+        .icp-icon {
+          width: 0.4rem;
+          height: 0.4rem;
+          margin-right: 0.1333rem;
+        }
+      }
+    }
+  }
+}
 </style>

+ 60 - 20
packages/web/src/components/header.vue

@@ -2,24 +2,35 @@
   <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 v-if="locale==='zh'" src="@/assets/img/logo_4dge_cn.png" alt="logo" />
-          <img v-if="locale==='en'" src="@/assets/img/logo_4dge_en.png" alt="logo" />
+        <a href="/">
+          <img v-if="locale === 'zh'" src="@/assets/img/logo_4dge_cn.png" alt="logo" />
+          <img v-if="locale === 'en'" src="@/assets/img/logo_4dge_en.png" alt="logo" />
         </a>
       </div>
       <div class="menu">
         <n-dropdown :options="options" @select="handleSelect">
-          <n-button type="primary" ghost>{{ locale === 'zh' ? $t('zh') : $t('en') }}</n-button>
+          <n-button v-if="!browser.isMobile" type="primary" ghost>{{
+            locale === 'zh' ? $t('zh') : $t('en')
+          }}</n-button>
+          <n-text v-else type="primary" ghost>{{ locale === 'zh' ? $t('zh') : $t('en') }}</n-text>
         </n-dropdown>
       </div>
     </div>
 
     <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-h1 class="help-text font-size-[48px] font-500">{{ $t('helperTip') }}</n-h1>
 
-      <n-auto-complete v-model:value="keySearch" :options="searchOptions" class="search-box max-w-[640px]" size="large"
-        round :placeholder="$t('enter_key')" blur-after-select
-        @select="(index: number) => handleAutoSelect(index, searchOptions)" @keydown.enter="handleSearch">
+      <n-auto-complete
+        v-model:value="keySearch"
+        :options="searchOptions"
+        class="search-box max-w-[640px]"
+        size="large"
+        round 
+        :placeholder="$t('enter_key')"
+        blur-after-select
+        @select="(index: number) => handleAutoSelect(index, searchOptions)"
+        @keydown.enter="handleSearch"
+      >
         <template #prefix>
           <n-icon :component="SearchOutline" />
         </template>
@@ -32,6 +43,7 @@ import { NH1, NIcon, NDropdown, NAutoComplete, NButton } from 'naive-ui'
 import { SearchOutline } from '@vicons/ionicons5'
 import { computedAsync } from '@vueuse/core'
 import { getArticleSearch } from '@/api'
+import browser from '@/utils/browser'
 
 const { t, locale } = useI18n()
 
@@ -49,8 +61,6 @@ const options = ref([
   },
 ])
 
-
-
 const searchOptions = computedAsync(
   async () => {
     if (keySearch.value) {
@@ -59,11 +69,10 @@ const searchOptions = computedAsync(
       return Array.from(list.data || []).map((suffix) => {
         return {
           label: suffix.title,
-          value: suffix.id
+          value: suffix.id,
         }
       })
     }
-
   },
   null, // initial state
 )
@@ -75,11 +84,11 @@ const handleSelect = (key: string) => {
   router.replace({
     name: route.name,
     query: {
-      lang: key
-    }
+      lang: key,
+    },
   })
   setTimeout(() => {
-    location.reload();
+    location.reload()
   }, 50)
 }
 onMounted(() => {
@@ -97,11 +106,9 @@ onMounted(() => {
   router.replace({
     name: route.name,
     query: {
-      lang: localeValue
-    }
+      lang: localeValue,
+    },
   })
-
-
 })
 const handleAutoSelect = (index: number, list: any[]) => {
   const item = list.find((it) => it.value === index)
@@ -114,7 +121,7 @@ const handleSearch = () => {
   router.push({ path: '/search', query: { key: keySearch.value } })
 }
 </script>
-<style>
+<style lang="scss" scoped>
 .header {
   background-image: url('@/assets/img/banner_bg1.png');
   background-repeat: no-repeat;
@@ -125,4 +132,37 @@ const handleSearch = () => {
 .search-box .n-input {
   --n-border-radius: 10px !important;
 }
+
+[is-mobile] {
+  .header {
+    padding: 0 0.4267rem;
+    box-sizing: border-box;
+    min-height: 1.3333rem;
+    width: 100%;
+    height: 7.7333rem;
+    background-image: url('@/assets/img/mobile/banner_bg1.png');
+    .logo {
+      img {
+        width: 2.2rem;
+        height: 0.5867rem;
+      }
+    }
+    .menu {
+      n-text {
+        font-size: 0.3733rem;
+      }
+    }
+  }
+  .search-box {
+    height: 1.0667rem;
+    border-radius: 0.5333rem;
+    .help-text {
+      font-weight: bold;
+      font-size: 0.5333rem;
+      color: #202020;
+      line-height: 0.6267rem;
+      margin-bottom: 0.76rem;
+    }
+  }
+}
 </style>

+ 32 - 9
packages/web/src/components/subHeader.vue

@@ -2,14 +2,17 @@
   <div class="header grid min-h-[160px] w-full bg-cyan-800 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="/"> 
+        <a href="/">
           <img v-if="locale === 'zh'" src="@/assets/img/logo_4dge_cn.png" alt="logo" />
           <img v-if="locale === 'en'" src="@/assets/img/logo_4dge_en.png" alt="logo" />
         </a>
       </div>
       <div class="menu">
         <n-dropdown :options="options" @select="handleSelect">
-          <n-button type="primary" ghost>{{ locale === 'zh' ? $t('zh') : $t('en') }}</n-button>
+          <n-button v-if="!browser.isMobile" type="primary" ghost>{{
+            locale === 'zh' ? $t('zh') : $t('en')
+          }}</n-button>
+          <n-text v-else type="primary" ghost>{{ locale === 'zh' ? $t('zh') : $t('en') }}</n-text>
         </n-dropdown>
       </div>
     </div>
@@ -17,6 +20,7 @@
 </template>
 <script setup lang="ts">
 import { NDropdown, NButton } from 'naive-ui'
+import browser from '@/utils/browser'
 // import { SearchOutline } from '@vicons/ionicons5'
 
 const { t, locale } = useI18n()
@@ -41,13 +45,12 @@ const handleSelect = (key: string) => {
   router.replace({
     name: route.name,
     query: {
-      lang: key
-    }
+      lang: key,
+    },
   })
   setTimeout(() => {
-    location.reload();
+    location.reload()
   }, 50)
-
 }
 onMounted(() => {
   let localeValue = localStorage.getItem('locale') || 'zh'
@@ -64,16 +67,36 @@ onMounted(() => {
   router.replace({
     name: route.name,
     query: {
-      lang: localeValue
-    }
+      lang: localeValue,
+    },
   })
 })
 </script>
-<style>
+<style lang="scss" scoped>
 .header {
   background-image: url('@/assets/img/banner_bg2.png');
   background-repeat: no-repeat;
   background-size: cover;
   background-position: top center;
 }
+
+[is-mobile] {
+  .header {
+    height: 2.1333rem;
+    min-height: auto;
+    padding: 0 0.5333rem;
+    box-sizing: border-box;
+  }
+  .logo {
+    img {
+      width: 2.2rem;
+      height: 0.5867rem;
+    }
+  }
+  .menu {
+    n-text {
+      font-size: 0.3733rem;
+    }
+  }
+}
 </style>

+ 8 - 1
packages/web/src/main.ts

@@ -5,7 +5,14 @@ import router from '@/plugins/router'
 import i18n from '@/plugins/i18n'
 import 'uno.css'
 // import '@unocss/reset/normalize.css'
-
+import browser from './utils/browser'
+if (browser.isMobile()) {
+  document.body.setAttribute('is-mobile', true)
+  var script = document.createElement('script')
+  script.src = '/static/js/flexible.js'
+  // script.async = true // 设置为异步加载
+  document.head.appendChild(script)
+}
 const app = createApp(App)
 
 app.use(router)

+ 223 - 94
packages/web/src/pages/index.vue

@@ -1,29 +1,37 @@
 <template>
   <div class="max-w-screen-xl content my-0 mx-auto">
-    <div v-for="(item, index) in list" :key="index" class="m-b-[80px]">
-      <template v-if="item.children?.length">
-        <n-h1 class="text-center mb-[80px] font-size-[40px] font-500">{{ item.title }}</n-h1>
-
-        <template v-if="item.styleType === 0">
-          <n-grid x-gap="100" y-gap="100" :cols="2">
-            <n-gi v-for="child of item.children" :key="child.id">
-              <div class="show-item b-rd-3xl relative" :class="{ [`style-${item.styleType}`]: true }"
-                :style="{ backgroundImage: `url(${child.cover})` }"
-                @click="handleToDoc(child as any as ArticleDetailType)">
+    <template v-for="(item, index) in list">
+      <div v-if="item.children?.length"  :key="index" class="title-box m-b-[80px]">
+        <template v-if="item.children?.length">
+          <n-h1 class="home-title text-center mb-[80px] font-size-[40px] font-500">{{
+            item.title
+          }}</n-h1>
+
+          <template v-if="item.styleType === 0">
+            <n-grid x-gap="100" y-gap="20" :cols="browser.isMobile() ? 1 : 2">
+              <n-gi v-for="child of item.children" :key="child.id">
                 <div
-                  class="w-full h-full flex flex-col absolute top-0 left-0 -mx-auto justify-end overflow-hidden shadow-blueGray">
+                  class="show-item b-rd-3xl relative"
+                  :class="{ [`style-${item.styleType}`]: true }"
+                  :style="{ backgroundImage: `url(${child.cover})` }"
+                  @click="handleToDoc(child as any as ArticleDetailType)"
+                >
                   <div
-                    class="w-full h-[60px] title text-black font-size-[20px] flex justify-center items-center bg-white bg-op-50">
-                    {{ child.title }}
+                    class="w-full h-full flex flex-col absolute top-0 left-0 -mx-auto justify-end overflow-hidden shadow-blueGray"
+                  >
+                    <div
+                      class="style-btn w-full h-[60px] title text-black font-size-[20px] flex justify-center items-center bg-white bg-op-50"
+                    >
+                      {{ child.title }}
+                    </div>
                   </div>
                 </div>
-              </div>
-            </n-gi>
-          </n-grid>
-        </template>
+              </n-gi>
+            </n-grid>
+          </template>
 
-        <template v-if="item.styleType === 1">
-          <!-- <swiper :slides-per-view="item.grid" :space-between="50" @swiper="onSwiper" @slideChange="onSlideChange">
+          <template v-if="item.styleType === 1">
+            <!-- <swiper :slides-per-view="item.grid" :space-between="50" @swiper="onSwiper" @slideChange="onSlideChange">
             <swiper-slide v-for="child of item.children" :key="child.id"
               @click="handleToDoc(child as any as ArticleDetailType)">
               <div class="cover w-full h-[400px] overflow-hidden b-rd-xl"
@@ -68,73 +76,102 @@
             </div>
 
           </swiper> -->
-          <swiper :grid="item.grid" :data="item.children"></swiper>
-        </template>
-
-        <template v-if="item.styleType === 2">
-          <n-grid x-gap="100" y-gap="100" :cols="2">
-            <n-gi v-for="child of item.children" :key="child.id">
-              <div @click="handleToDoc(child as any as ArticleDetailType)"
-                :class="{ [`style-${item.styleType}`]: true }" class="show-item b-rd-3xl relative w-full h-full">
-                <img :src="child.cover" alt="" class="absolute w-[128px] h-[128px] left-0 top-[-40px]" />
+            <swiper :grid="browser.isMobile() ? 2.5 : item.grid" :data="item.children"></swiper>
+          </template>
+
+          <template v-if="item.styleType === 2">
+            <n-grid
+              x-gap="100"
+              :y-gap="browser.isMobile() ? 30 : 100"
+              :cols="browser.isMobile() ? 1 : 2"
+              class="device-list"
+            >
+              <n-gi v-for="child of item.children" :key="child.id">
+                <div
+                  @click="handleToDoc(child as any as ArticleDetailType)"
+                  :class="{ [`style-${item.styleType}`]: true }"
+                  class="show-item b-rd-3xl relative w-full h-full"
+                >
+                  <img
+                    :src="child.cover"
+                    alt=""
+                    class="cover-product absolute w-[128px] h-[128px] left-0 top-[-40px]"
+                  />
+
+                  <div class="tab pl-[128px] w-[calc(100%-128px)] overflow-hidden">
+                    <div
+                      class="device-title font-size-[20px] whitespace-nowrap text-ellipsis font-bold mt-[25px] w-[calc(100%-20px)] overflow-hidden"
+                    >
+                      {{ child.title }}
+                    </div>
+
+                    <div
+                      class="device-tag text-size-base whitespace-nowrap text-ellipsis w-[calc(100%-20px)] overflow-hidden color-[#909090]"
+                    >
+                      {{ child.description }}
+                    </div>
+                  </div>
+                </div>
+              </n-gi>
+            </n-grid>
+          </template>
+
+          <template v-if="item.styleType === 3">
+            <n-grid
+              x-gap="100"
+              :y-gap="browser.isMobile() ? 16 : 100"
+              :cols="browser.isMobile() ? 1 : 3"
+              class="software-list"
+            >
+              <n-gi v-for="child of item.children" :key="child.id">
+                <div
+                  @click="handleToDoc(child as any as ArticleDetailType)"
+                  :class="{ [`style-${item.styleType}`]: true }"
+                  class="show-item b-rd-3xl relative w-full h-full flex flex-col align-center items-center justify-center"
+                >
+                  <img :src="child.cover" alt="" class="w-[40px] h-[40px] software-icon" />
 
-                <div class="pl-[128px] w-[calc(100%-128px)] overflow-hidden">
-                  <div
-                    class="font-size-[20px] whitespace-nowrap text-ellipsis font-bold mt-[25px] w-[calc(100%-20px)] overflow-hidden">
+                  <div class="software-title font-size-[20px] font-bold my-[16px]">
                     {{ child.title }}
                   </div>
 
-                  <div
-                    class="text-size-base whitespace-nowrap text-ellipsis w-[calc(100%-20px)] overflow-hidden color-[#909090]">
+                  <div class="software-desc color-[#909090] w-[calc(100%-30px)] text-center">
                     {{ child.description }}
                   </div>
                 </div>
-              </div>
-            </n-gi>
-          </n-grid>
-        </template>
-
-        <template v-if="item.styleType === 3">
-          <n-grid x-gap="100" y-gap="100" :cols="3">
-            <n-gi v-for="child of item.children" :key="child.id">
-              <div @click="handleToDoc(child as any as ArticleDetailType)"
-                :class="{ [`style-${item.styleType}`]: true }"
-                class="show-item b-rd-3xl relative w-full h-full flex flex-col align-center items-center justify-center">
-                <img :src="child.cover" alt="" class="w-[40px] h-[40px]" />
-
-                <div class="font-size-[20px] font-bold my-[16px]">{{ child.title }}</div>
-
-                <div class="color-[#909090] w-[calc(100%-30px)] text-center">
-                  {{ child.description }}
+              </n-gi>
+            </n-grid>
+          </template>
+          <template v-if="item.styleType === 4">
+            <!-- {{ otherstyleEnum }} -->
+            <n-grid x-gap="100" y-gap="20" :cols="browser.isMobile() ? 1 : 2" class="other-list">
+              <n-gi v-for="other of otherstyleEnum" :key="other.value">
+                <div :class="{ [`style-${item.styleType}`]: true }" class="show-item">
+                  <n-h5 class="other-desk font-size-[16px] font-800">
+                    {{ other.value === 0 ? $t('developer') : $t('recommend_reading') }}
+                  </n-h5>
+                  <ul class="otherList">
+                    <li
+                      class="font-size-[14px]"
+                      @click="handleToDoc(child as any as ArticleDetailType)"
+                      v-for="child of item.children
+                        .filter((c) => c.otherType === other.value)
+                        .slice(0, 3)"
+                      :key="child.id"
+                    >
+                      <span>{{ child.title }}</span>
+                    </li>
+                    <li
+                      v-if="item.children.filter((c) => c.otherType === other.value).length > 3"
+                      class="font-size-[14px] more"
+                      @click="handleToMore(item as any as ArticleDetailType)"
+                    >
+                      <span class="font-medium">{{ $t('more') }}>></span>
+                    </li>
+                  </ul>
                 </div>
-              </div>
-            </n-gi>
-          </n-grid>
-        </template>
-        <template v-if="item.styleType === 4">
-          <!-- {{ otherstyleEnum }} -->
-          <n-grid x-gap="100" y-gap="20" :cols="2">
-            <n-gi v-for="other of otherstyleEnum" :key="other.value">
-
-              <div :class="{ [`style-${item.styleType}`]: true }" class="show-item">
-                <n-h5 class="font-size-[16px] font-800">
-                  {{ other.value === 0 ? $t('developer') : $t('recommend_reading') }}
-                </n-h5>
-                <ul class="otherList">
-                  <li class="font-size-[14px]" @click="handleToDoc(child as any as ArticleDetailType)"
-                    v-for="child of item.children.filter(c => c.otherType === other.value).slice(0, 3)" :key="child.id">
-                    <span>{{ child.title }}</span>
-                  </li>
-                  <li v-if="item.children.filter(c => c.otherType === other.value).length > 3"
-                    class="font-size-[14px] more" @click="handleToMore(item as any as ArticleDetailType)">
-                    <span class="font-medium">{{ $t('more') }}>></span>
-                  </li>
-
-                </ul>
-              </div>
-
-            </n-gi>
-            <!-- <n-gi v-for="child of item.children" :key="child.id">
+              </n-gi>
+              <!-- <n-gi v-for="child of item.children" :key="child.id">
               <div
                 @click="handleToDoc(child as any as ArticleDetailType)"
                 :class="{ [`style-${item.styleType}`]: true }"
@@ -149,11 +186,11 @@
                 </div>
               </div>
             </n-gi> -->
-          </n-grid>
+            </n-grid>
+          </template>
         </template>
-
-      </template>
-    </div>
+      </div>
+    </template>
   </div>
 </template>
 
@@ -172,11 +209,10 @@ import type { MenuItem } from '@/api'
 // import { Swiper, SwiperSlide } from 'swiper/vue'
 import swiper from './swiper.vue'
 // import 'swiper/css'
+import browser from '@/utils/browser'
 import router from '@/plugins/router.ts'
 const { t } = useI18n()
 
-
-
 const otherstyleEnum = [
   {
     value: 0,
@@ -189,7 +225,7 @@ const otherstyleEnum = [
 ]
 
 const list = ref<MenuItem[]>([])
-const swiperInstance = ref();
+const swiperInstance = ref()
 const activeIndex = ref(0)
 getMenuList().then((data) => {
   if (data.data) {
@@ -204,10 +240,9 @@ const onSwiper = (swiper: unknown) => {
   }
 }
 
-
 const handleToDoc = (child: ArticleDetailType) => {
   const { articleId, categoryId } = child
-  console.log("handleToDoc", child)
+  console.log('handleToDoc', child)
   if (articleId) {
     router.push({ path: `/showdoc/${articleId}` })
   } else {
@@ -216,15 +251,15 @@ const handleToDoc = (child: ArticleDetailType) => {
 }
 const handleToMore = (item: ArticleDetailType) => {
   if (item && item.children && item.children.length > 0) {
-    const cateId = item.children[0].categoryId;
+    const cateId = item.children[0].categoryId
     console.log('cateId', cateId)
     router.push({ path: `/showcate/${cateId}` })
   }
 }
 const onSlideChange = (swiper) => {
-  console.log('slide change', swiper);
+  console.log('slide change', swiper)
   activeIndex.value = swiper.activeIndex
-};
+}
 const handlePrev = () => {
   console.log('handlePrev')
   swiperInstance.value.slidePrev()
@@ -232,7 +267,6 @@ const handlePrev = () => {
 const handleNext = () => {
   console.log('handleNext')
   swiperInstance.value.slideNext()
-
 }
 </script>
 
@@ -242,7 +276,7 @@ const handleNext = () => {
 }
 
 .icon {
-  color: #0661C9;
+  color: #0661c9;
   margin: 0 5px;
 
   &.disable {
@@ -290,7 +324,7 @@ const handleNext = () => {
   &.style-4 {
     width: 482px;
     min-height: 200px;
-    background: #F5F9FF;
+    background: #f5f9ff;
     border-radius: 10px 10px 10px 10px;
     padding: 30px 64px;
     cursor: default;
@@ -309,7 +343,7 @@ const handleNext = () => {
       }
 
       &.more {
-        color: #0661C9;
+        color: #0661c9;
 
         &:hover {
           color: #107bf5;
@@ -318,4 +352,99 @@ const handleNext = () => {
     }
   }
 }
+[is-mobile] {
+  .title-box {
+    margin-bottom: 0;
+    margin-top: 1.0667rem;
+    &:first-of-type {
+      margin-top: 0;
+    }
+    .home-title {
+      font-weight: bold;
+      font-size: 0.4267rem;
+      color: #202020;
+      line-height: 0.5067rem;
+      margin-bottom: 0.5333rem;
+    }
+  }
+  .style-0 {
+    width: 8.9333rem;
+    height: 3.7867rem;
+    margin: 0 auto;
+    border-radius: 0.2667rem;
+    .style-btn {
+      height: 0.8533rem;
+      font-size: 0.32rem;
+    }
+  }
+  .device-list {
+    padding: 0 0.5333rem;
+    box-sizing: border-box;
+  }
+  .style-2 {
+    width: auto;
+    height: 1.6rem;
+    .cover-product {
+      width: 1.92rem;
+      height: 1.92rem;
+      top: -0.4267rem;
+      left: 0.1067rem;
+    }
+    .tab {
+      padding-left: 2.2933rem;
+      .device-title {
+        font-size: 0.32rem;
+        margin-top: 0.3333rem;
+      }
+      .device-tag {
+        font-size: 0.2667rem;
+        margin-top: 0.1067rem;
+      }
+    }
+  }
+  .software-list {
+    padding: 0 0.5333rem;
+    box-sizing: border-box;
+    .style-3 {
+      width: 100%;
+      height: 4.2667rem;
+      .software-icon {
+        width: 1.0133rem;
+        height: 1.0133rem;
+      }
+      .software-title {
+        font-size: 0.3733rem;
+        line-height: 0.44rem;
+      }
+      .software-desc {
+        font-weight: 400;
+        font-size: 0.2667rem;
+      }
+    }
+  }
+  .other-list {
+    padding: 0 0.5333rem;
+    box-sizing: border-box;
+    margin-bottom: 1.0667rem;
+    .style-4 {
+      width: 100%;
+      box-sizing: border-box;
+      padding: 0.8rem 0.64rem;
+      .other-desk {
+        font-weight: bold;
+        font-size: 0.3733rem;
+        line-height: 0.44rem;
+      }
+      .otherList {
+        li {
+          span {
+            font-weight: 500;
+            font-size: 0.32rem;
+            line-height: 0.3733rem;
+          }
+        }
+      }
+    }
+  }
+}
 </style>

+ 23 - 3
packages/web/src/pages/search.vue

@@ -2,8 +2,8 @@
   <div>
     <!--    {{ list }}-->
 
-    <div class="max-w-screen-lg my-[50px] mx-auto">
-      <n-h2> {{$t('search_res')}}:</n-h2>
+    <div class="search-box max-w-screen-lg my-[50px] mx-auto">
+      <n-h2 class="title"> {{ $t('search_res') }}:</n-h2>
       <template v-if="list.length > 0">
         <n-list hoverable clickable>
           <template v-for="(item, index) in list" :key="index">
@@ -19,7 +19,9 @@
           </template>
         </n-list>
 
-        <div class="my-[50px] mx-auto text-center color-[#909090]">{{ $t('all') }} {{ list.length }}  {{ $t('record') }}</div>
+        <div class="my-[50px] mx-auto text-center color-[#909090]">
+          {{ $t('all') }} {{ list.length }} {{ $t('record') }}
+        </div>
       </template>
       <n-empty
         v-else
@@ -72,3 +74,21 @@ const handleToArticle = (item: ArticleDetailType) => {
   router.push(`/showdoc/${item.id}`)
 }
 </script>
+<style lang="scss" scoped>
+.search-box {
+  padding: 0 0.5333rem;
+  .title {
+    font-size: 0.4267rem;
+    font-weight: bold;
+  }
+  :deep(.n-list-item) {
+    padding: 0.2667rem 0;
+    .n-thing-header__title {
+      font-size: 0.3733rem;
+    }
+    .n-thing-main__description {
+      font-size: 0.32rem;
+    }
+  }
+}
+</style>

+ 161 - 22
packages/web/src/pages/showcate/[id].vue

@@ -1,12 +1,13 @@
 <template>
   <div class="max-w-screen-xl content my-0 mx-auto text-size-base overflow-hidden">
-
     <div>
       <div class="breadcrumb my-[30px] pb-[20px] bb-1px_#EBEBEB" role="navigation">
         <n-breadcrumb separator=">" v-if="breadcrumb">
           <n-breadcrumb-item @click="$router.push('/')">{{ $t('home') }}</n-breadcrumb-item>
           <template v-for="(bread, index) in breadcrumb" :key="index">
-            <n-breadcrumb-item @click="handleBreadcrumb(bread)"> {{ bread.title }}</n-breadcrumb-item>
+            <n-breadcrumb-item @click="handleBreadcrumb(bread)">
+              {{ bread.title }}</n-breadcrumb-item
+            >
           </template>
         </n-breadcrumb>
       </div>
@@ -16,18 +17,63 @@
         <!-- <n-h2 class="mb-10"> {{ currentCate.title }}</n-h2> -->
       </div>
 
-      <div class="w-full flex flex-row flex-nowrap">
-        <div class="flex-basis-[240px] flex-grow-0 flex-shrink-0 overflow-hidden br-1px_#EBEBEB">
-          <n-tree class="left-tree" v-if="mainCategories" v-model:selected-keys="selectedKeys" block-line
-            :data="mainCategories.children" :default-expanded-keys="defaultExpandedKeys" :node-props="nodeProps"
-            key-field="id" label-field="title" children-field="children" selectable>
+      <div class="waper-content w-full flex flex-row flex-nowrap">
+        <div
+          class="waper-left flex-basis-[240px] flex-grow-0 flex-shrink-0 overflow-hidden br-1px_#EBEBEB"
+          :class="{ open: isOpenMuen }"
+        >
+          <div class="open-btn" @click="toggleOpen">
+            <svg
+              xmlns="http://www.w3.org/2000/svg"
+              xmlns:xlink="http://www.w3.org/1999/xlink"
+              viewBox="0 0 512 512"
+            >
+              <path
+                fill="none"
+                stroke="currentColor"
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="32"
+                d="M96 256h320"
+              ></path>
+              <path
+                fill="none"
+                stroke="currentColor"
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="32"
+                d="M96 176h320"
+              ></path>
+              <path
+                fill="none"
+                stroke="currentColor"
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="32"
+                d="M96 336h320"
+              ></path>
+            </svg>
+          </div>
+          <n-tree
+            class="left-tree"
+            v-if="mainCategories"
+            v-model:selected-keys="selectedKeys"
+            block-line
+            :data="mainCategories.children"
+            :default-expanded-keys="defaultExpandedKeys"
+            :node-props="nodeProps"
+            key-field="id"
+            label-field="title"
+            children-field="children"
+            selectable
+          >
             <template #empty>
               <n-empty :description="$t('no_data')" />
             </template>
           </n-tree>
         </div>
 
-        <div class="flex-1 w-[calc(100%-80px)] px-[40px] mb-[120px] overflow-hidden">
+        <div class="waper-right flex-1 w-[calc(100%-80px)] px-[40px] mb-[120px] overflow-hidden">
           <template v-if="articleList.length > 0">
             <ul class="cate-list">
               <li v-for="article in articleList" :key="article.id">
@@ -39,14 +85,10 @@
             <div class="pt-20">
               <n-empty :description="$t('no_data')" />
             </div>
-
           </template>
-
         </div>
 
-        <div class="flex-basis-[240px] flex-grow-0 flex-shrink-0 overflow-hidden">
-
-        </div>
+        <div class="flex-basis-[240px] flex-grow-0 flex-shrink-0 overflow-hidden"></div>
       </div>
     </div>
   </div>
@@ -66,17 +108,16 @@ import {
   type ArticleDetailType,
   type CategoryItem,
   getCategoryTree,
-  getArticlesByCateId
+  getArticlesByCateId,
 } from '@/api'
 
 import { NH2, NBreadcrumb, NBreadcrumbItem, NTree, NEmpty } from 'naive-ui'
 import type { TreeOption } from 'naive-ui'
 import { findNodeById, findBreadcrumbPath, type TreeNode } from '@/utils'
 
-
 const route = useRoute()
 const router = useRouter()
-
+const isOpenMuen = ref<Boolean>(false)
 const params = route.params as {
   id?: number
 }
@@ -86,7 +127,9 @@ const mainCategories = ref<CategoryItem>()
 const articleList = ref<ArticleDetailType[]>([])
 const defaultExpandedKeys = ref<number[]>([])
 const selectedKeys = ref<number[]>([])
-
+const toggleOpen = () => {
+  isOpenMuen.value = !isOpenMuen.value
+}
 const nodeProps = ({ option }: { option: TreeOption }) => {
   return {
     async onClick() {
@@ -102,7 +145,7 @@ const nodeProps = ({ option }: { option: TreeOption }) => {
 watchEffect(async () => {
   if (params.id) {
     if (!isNaN(+params.id)) {
-      const res = await getArticlesByCateId(params.id);
+      const res = await getArticlesByCateId(params.id)
       if (res) {
         articleList.value = res.data as ArticleDetailType[]
       }
@@ -116,13 +159,17 @@ watchEffect(async () => {
         ) as unknown as CategoryItem
         console.log('currentCate', currentCate.value)
         defaultExpandedKeys.value = [currentCate.value.parentId]
-        const selectID = currentCate.value.parentId ? currentCate.value.id : currentCate.value.children[0].id
-        selectedKeys.value = [selectID];
-        breadcrumb.value = findBreadcrumbPath([mainCategories.value] as unknown as TreeNode[], Number(params.id))
+        const selectID = currentCate.value.parentId
+          ? currentCate.value.id
+          : currentCate.value.children[0].id
+        selectedKeys.value = [selectID]
+        breadcrumb.value = findBreadcrumbPath(
+          [mainCategories.value] as unknown as TreeNode[],
+          Number(params.id),
+        )
         console.log('breadcrumb', [mainCategories.value], breadcrumb.value)
       }
     }
-
   }
 })
 
@@ -172,4 +219,96 @@ const handleBreadcrumb = (bread: TreeNode) => {
     }
   }
 }
+.open-btn {
+  display: none;
+  svg {
+    width: 100%;
+    height: 100%;
+  }
+}
+[is-mobile] {
+  .breadcrumb {
+    height: 0.88rem;
+    padding: 0;
+    margin: 0;
+    display: flex;
+    align-items: center;
+    justify-content: flex-start;
+    padding:0 .5333rem;
+    overflow-x: auto;
+    :deep(.n-breadcrumb) {
+      ul {
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+      }
+    }
+  }
+  .content {
+    min-height: auto;
+  }
+  .waper-content {
+    display: block;
+    position: relative;
+    .waper-left {
+      position: fixed;
+      left: 0;
+      top: 5.0667rem;
+      z-index: 1;
+      background: #fff;
+      width: 6.4rem;
+      height: 30%;
+      border-right: 1px #ebebeb solid;
+      border-top: 1px #ebebeb solid;
+      border-bottom: 1px #ebebeb solid;
+      overflow: visible;
+      transition: all 0.3s;
+      transform: translateX(-100%);
+      &.open {
+        transform: translateX(0);
+      }
+      :deep(.left-tree) {
+        // --n-node-content-height: 40px !important;
+        // //--n-node-color-hover: rgba(6,97,201,0.06) !important;
+        // //border-right: 1px solid #e5e7eb;
+
+        width: 100%;
+        height: 100%;
+        overflow-y: auto;
+        .n-tree-node-wrapper {
+          width: 100%;
+          padding: 0;
+        }
+      }
+      .open-btn {
+        position: absolute;
+        right: calc(-0.8rem - 1px);
+        top: -1px;
+        width: 0.8rem;
+        height: 0.8533rem;
+        border-radius: 0px 0.1067rem 0.1067rem 0px;
+        border: 1px solid #ebebeb;
+        display: block;
+        background: #fff;
+        z-index: 2;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+      }
+    }
+    .waper-right {
+      width: 100%;
+      padding: 0 0.8rem;
+      box-sizing: border-box;
+      margin-bottom: 0.8rem;
+      .cate-list {
+        li {
+          span {
+            font-size: 0.5333rem;
+          }
+        }
+      }
+    }
+  }
+}
 </style>

+ 360 - 40
packages/web/src/pages/showdoc/[id].vue

@@ -1,59 +1,122 @@
 <template>
-  <div class="max-w-screen-xl content my-0 mx-auto text-size-base overflow-hidden"
-    :class="!detail ? 'flex flex-column justify-center items-center h-full' : ''">
+  <div
+    class="max-w-screen-xl content my-0 mx-auto text-size-base overflow-hidden"
+    :class="!detail ? 'flex flex-column justify-center items-center h-full' : ''"
+  >
     <div v-if="detail">
       <div class="breadcrumb my-[30px] pb-[20px] bb-1px_#EBEBEB" role="navigation">
         <n-breadcrumb separator=">" v-if="breadcrumb">
           <n-breadcrumb-item @click="$router.push('/')">{{ $t('home') }}</n-breadcrumb-item>
           <template v-for="(bread, index) in breadcrumb" :key="index">
-            <n-breadcrumb-item @click="handleBreadcrumb(bread)"> {{ bread.title }}</n-breadcrumb-item>
+            <n-breadcrumb-item @click="handleBreadcrumb(bread)">
+              {{ bread.title }}</n-breadcrumb-item
+            >
           </template>
         </n-breadcrumb>
       </div>
       <div v-if="currentCate">
-        <n-h2 class="mb-10"> {{ currentCate.title }}</n-h2>
+        <n-h2 class="doc-title mb-10"> {{ currentCate.title }}</n-h2>
       </div>
-      <div class="w-full flex flex-row flex-nowrap">
-        <div class="flex-basis-[240px] flex-grow-0 flex-shrink-0 br-1px_#EBEBEB overflow-hidden">
-
-          <n-tree class="left-tree" v-model:selected-keys="selectedKeys" v-if="mainCategories" block-line
-            :data="mainCategories.children" :default-expanded-keys="defaultExpandedKeys" :node-props="nodeProps"
-            key-field="id" label-field="title" children-field="children" selectable>
+      <div class="waper-content w-full flex flex-row flex-nowrap">
+        <div
+          class="waper-menu flex-basis-[240px] flex-grow-0 flex-shrink-0 br-1px_#EBEBEB overflow-hidden"
+          :class="{ open: isOpenMuen }"
+        >
+          <div class="open-btn" @click="toggleOpen">
+            <svg
+              xmlns="http://www.w3.org/2000/svg"
+              xmlns:xlink="http://www.w3.org/1999/xlink"
+              viewBox="0 0 512 512"
+            >
+              <path
+                fill="none"
+                stroke="currentColor"
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="32"
+                d="M96 256h320"
+              ></path>
+              <path
+                fill="none"
+                stroke="currentColor"
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="32"
+                d="M96 176h320"
+              ></path>
+              <path
+                fill="none"
+                stroke="currentColor"
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="32"
+                d="M96 336h320"
+              ></path>
+            </svg>
+          </div>
+          <n-tree
+            class="left-tree"
+            v-model:selected-keys="selectedKeys"
+            v-if="mainCategories"
+            block-line
+            :data="mainCategories.children"
+            :default-expanded-keys="defaultExpandedKeys"
+            :node-props="nodeProps"
+            key-field="id"
+            label-field="title"
+            children-field="children"
+            selectable
+          >
             <template #empty>
               <n-empty :description="$t('no_data')" />
             </template>
           </n-tree>
-
         </div>
-        <div class="flex-1 pages w-[calc(100%-80px)] px-[40px] mb-[80px] overflow-hidden">
-          <div class="bb-1px_#EBEBEB color-[#999999]">
+        <div class="infos flex-1 pages w-[calc(100%-80px)] px-[40px] mb-[80px] overflow-hidden">
+          <div class="box 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>{{ $t('publish') }} {{ dayjs(detail.createTime).format('YYYY-MM-DD') }}</p>
+              <p class="time">
+                {{ $t('publish') }} {{ dayjs(detail.createTime).format('YYYY-MM-DD') }}
+              </p>
               <!-- <p>{{ $t('read') }} ( {{ detail.readCount }} )</p> -->
             </span>
           </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="top-clt 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" :class="{ articleNear: articleNear[0] }"
-                @click="handleToArticle(articleNear[0])">
-                {{ $t('prev_article') }} </span>
-              <span class="flex-1 articleNear" v-if="articleNear[0]" @click="handleToArticle(articleNear[0])">
+              <span
+                class="flex-1 text-align-left"
+                :class="{ articleNear: articleNear[0] }"
+                @click="handleToArticle(articleNear[0])"
+              >
+                {{ $t('prev_article') }}
+              </span>
+              <span
+                class="flex-1 articleNear"
+                v-if="articleNear[0]"
+                @click="handleToArticle(articleNear[0])"
+              >
                 {{ articleNear[0].title }}
               </span>
               <span v-else>
                 {{ $t('no_data') }}
               </span>
-
             </div>
-            <div class="next flex flex-col ">
-              <span class="flex-1  text-align-right" :class="{ articleNear: articleNear[1] }"
-                @click="handleToArticle(articleNear[1])">
+            <div class="next flex flex-col">
+              <span
+                class="flex-1 text-align-right"
+                :class="{ articleNear: articleNear[1] }"
+                @click="handleToArticle(articleNear[1])"
+              >
                 {{ $t('next_article') }}
               </span>
-              <span class="flex-1 articleNear" v-if="articleNear[1]" @click="handleToArticle(articleNear[1])">
+              <span
+                class="flex-1 articleNear"
+                v-if="articleNear[1]"
+                @click="handleToArticle(articleNear[1])"
+              >
                 {{ articleNear[1].title }}
               </span>
               <span v-else>
@@ -64,12 +127,17 @@
           <n-back-top to=".pages" :visibility-height="800" />
         </div>
 
-        <div class="flex-basis-[240px] flex-grow-0 flex-shrink-0 overflow-hidden">
+        <div class="desc-list flex-basis-[240px] flex-grow-0 flex-shrink-0 overflow-hidden">
           <div class="min-h-[200px] bg-[#F5F9FF] b-r-[8px] p-[20px]">
             <n-h4 class="font-600 ext-size-base">{{ $t('main_content') }}</n-h4>
             <n-anchor :show-rail="false">
-              <n-anchor-link class="text-size-base color-[#999999]" v-for="(item, index) in mainContents" :key="index"
-                :title="item.text" :href="`#my-section-${index + 1}`">
+              <n-anchor-link
+                class="text-size-base color-[#999999]"
+                v-for="(item, index) in mainContents"
+                :key="index"
+                :title="item.text"
+                :href="`#my-section-${index + 1}`"
+              >
               </n-anchor-link>
             </n-anchor>
           </div>
@@ -77,7 +145,6 @@
       </div>
     </div>
     <div v-else>
-
       <n-empty :description="$t('no_article')">
         <template #extra>
           {{ $t('no_data_article') }},
@@ -111,13 +178,32 @@ import {
   // getArticlesByCateId,
   getNearArticles,
 } from '@/api'
-import { htmlToTree, createAnchorNames, dayjs, findNodeById, findBreadcrumbPath, type TreeNode } from '@/utils'
-import { NH1, NH2, NH4, NBackTop, NEmpty, NButton, NBreadcrumb, NBreadcrumbItem, NTree, NAnchor, NAnchorLink } from 'naive-ui'
+import {
+  htmlToTree,
+  createAnchorNames,
+  dayjs,
+  findNodeById,
+  findBreadcrumbPath,
+  type TreeNode,
+} from '@/utils'
+import {
+  NH1,
+  NH2,
+  NH4,
+  NBackTop,
+  NEmpty,
+  NButton,
+  NBreadcrumb,
+  NBreadcrumbItem,
+  NTree,
+  NAnchor,
+  NAnchorLink,
+} from 'naive-ui'
 import type { TreeOption } from 'naive-ui'
 
 const route = useRoute()
 const router = useRouter()
-const selectedKeys = ref([]);
+const selectedKeys = ref([])
 const params = route.params as {
   id?: number
 }
@@ -129,7 +215,10 @@ const currentCate = ref<CategoryItem | undefined>()
 const mainCategories = ref<CategoryItem[]>([])
 const defaultExpandedKeys = ref<number[]>([])
 const articleNear = ref<ArticleDetailType[]>([])
-
+const isOpenMuen = ref<Boolean>(false)
+const toggleOpen = () => {
+  isOpenMuen.value = !isOpenMuen.value
+}
 const nodeProps = ({ option }: { option: TreeOption }) => {
   return {
     async onClick() {
@@ -165,7 +254,6 @@ watchEffect(() => {
           document.title = detail.value.title
           mainContents.value = htmlToTree(detail.value.content)
           if (detail.value.categoryId) {
-
             const res = await getCategoryTree(detail.value.categoryId)
             if (res.data) {
               mainCategories.value = res.data as CategoryItem[]
@@ -175,16 +263,21 @@ watchEffect(() => {
                   detail.value.categoryId,
                 ) as unknown as CategoryItem
                 defaultExpandedKeys.value = [currentCate.value.parentId]
-                const selectID = currentCate.value.parentId ? currentCate.value.id : currentCate.value.children[0].id
-                selectedKeys.value = [selectID];
-                breadcrumb.value = findBreadcrumbPath([mainCategories.value] as unknown as TreeNode[], detail.value.categoryId)
+                const selectID = currentCate.value.parentId
+                  ? currentCate.value.id
+                  : currentCate.value.children[0].id
+                selectedKeys.value = [selectID]
+                breadcrumb.value = findBreadcrumbPath(
+                  [mainCategories.value] as unknown as TreeNode[],
+                  detail.value.categoryId,
+                )
                 console.log('breadcrumb', [mainCategories.value], breadcrumb.value)
               }
             }
           }
         }
       })
-      getNearArticles(+params.id).then(res => {
+      getNearArticles(+params.id).then((res) => {
         if (res.data) {
           articleNear.value = res.data
         }
@@ -206,7 +299,6 @@ const handleBreadcrumb = (bread: TreeNode) => {
     location.reload()
   }, 50)
 }
-
 </script>
 
 <style lang="scss" scoped>
@@ -221,8 +313,8 @@ const handleBreadcrumb = (bread: TreeNode) => {
 }
 
 :deep(.n-back-top) {
-  position: absolute;
-  bottom: 0;
+  // position: absolute;
+  // bottom: 0;
 }
 
 :deep(.left-tree) {
@@ -242,4 +334,232 @@ const handleBreadcrumb = (bread: TreeNode) => {
     color: #5a5a5a;
   }
 }
+.open-btn {
+  display: none;
+  svg {
+    width: 100%;
+    height: 100%;
+  }
+}
+
+[is-mobile] {
+  .breadcrumb {
+    height: 0.88rem;
+    padding: 0;
+    margin: 0;
+    display: flex;
+    align-items: center;
+    justify-content: flex-start;
+    padding: 0 0.5333rem;
+    overflow-x: auto;
+    :deep(.n-breadcrumb) {
+      ul {
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+      }
+    }
+  }
+  .doc-title {
+    font-weight: bold;
+    font-size: 0.3733rem;
+    color: #000000;
+    line-height: 0.44rem;
+    padding: 0.5333rem;
+    box-sizing: border-box;
+    margin: 0;
+  }
+  .content {
+    min-height: auto;
+  }
+
+  .waper-content {
+    display: block;
+    position: relative;
+    flex-direction: column;
+    :deep(.n-back-top) {
+      right: 0.1333rem !important;
+      width: 0.8rem !important;
+      height: 0.8rem !important;
+      padding: 0 !important;
+      min-width: auto !important;
+      .n-base-icon {
+        font-size: 0.5333rem !important;
+      }
+    }
+    .waper-menu {
+      position: fixed;
+      left: 0;
+      top: 5.0667rem;
+      z-index: 1;
+      background: #fff;
+      width: 6.4rem;
+      height: 30%;
+      border-right: 1px #ebebeb solid;
+      border-top: 1px #ebebeb solid;
+      border-bottom: 1px #ebebeb solid;
+      overflow: visible;
+      transition: all 0.3s;
+      transform: translateX(-100%);
+      &.open {
+        transform: translateX(0);
+      }
+      :deep(.left-tree) {
+        // --n-node-content-height: 40px !important;
+        // //--n-node-color-hover: rgba(6,97,201,0.06) !important;
+        // //border-right: 1px solid #e5e7eb;
+
+        width: 100%;
+        height: 100%;
+        overflow-y: auto;
+        .n-tree-node-wrapper {
+          width: 100%;
+          padding: 0;
+        }
+      }
+      .open-btn {
+        position: absolute;
+        right: calc(-0.8rem - 1px);
+        top: -1px;
+        width: 0.8rem;
+        height: 0.8533rem;
+        border-radius: 0px 0.1067rem 0.1067rem 0px;
+        border: 1px solid #ebebeb;
+        display: block;
+        background: #fff;
+        z-index: 2;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+      }
+    }
+    .infos {
+      width: 100%;
+      padding: 0 0.8rem;
+      box-sizing: border-box;
+      margin-bottom: 1.6rem;
+      .box {
+        font-weight: 500;
+        font-size: 0.5333rem;
+        color: #000000;
+        line-height: 0.8rem;
+        .time {
+          font-size: 0.2667rem;
+          color: #999999;
+          line-height: 0.3067rem;
+        }
+      }
+
+      :deep(.content-html) {
+        padding: 0.8rem 0;
+        h1 {
+          font-weight: bold;
+          font-size: 0.4rem;
+          color: #000000;
+          line-height: 0.48rem;
+          margin: 0.2667rem 0;
+        }
+        h2 {
+          font-weight: bold;
+          font-size: 0.36rem;
+          color: #000000;
+          line-height: 0.48rem;
+          margin: 0.2667rem 0;
+        }
+        h3,
+        h4,
+        h5,
+        h6 {
+          margin: 0.2667rem 0;
+          font-weight: bold;
+          font-size: 0.32rem;
+          color: #000000;
+          line-height: 0.48rem;
+        }
+
+        p {
+          font-weight: 400;
+          font-size: 0.32rem;
+          color: #000000;
+          line-height: 0.48rem;
+        }
+        video{
+          width: 100%;
+          height: auto;
+        }
+        img {
+          // width: auto;
+          // height: auto;
+          max-height: 100%;
+          max-width: 100%;
+        }
+      }
+      .top-clt {
+        flex-direction: row;
+        justify-content: space-between;
+        padding-top: 0.8rem;
+        .prev {
+          font-size: 0.2667rem;
+          display: flex;
+          width: 45%;
+          justify-content: flex-start;
+          .articleNear {
+            width: 100%;
+            font-size: 0.32rem;
+            color: #999898;
+            line-height: 0.3733rem;
+            overflow: hidden;
+            white-space: nowrap;
+            text-overflow: ellipsis;
+          }
+          span {
+            font-size: 0.32rem;
+            color: #999898;
+            // line-height: 0.3733rem;
+            text-align: left;
+            width: 100%;
+            overflow: hidden;
+            white-space: nowrap;
+            text-overflow: ellipsis;
+          }
+        }
+        .next {
+          font-size: 0.2667rem;
+          display: flex;
+          width: 45%;
+          justify-content: flex-end;
+          span {
+            font-size: 0.32rem;
+            color: #999898;
+            // line-height: 0.3733rem;
+            text-align: right;
+            width: 100%;
+            overflow: hidden;
+            white-space: nowrap;
+            text-overflow: ellipsis;
+          }
+        }
+      }
+    }
+  }
+  .desc-list {
+    padding: 0 0.8rem 1.6rem;
+    > div {
+      padding: 0.8rem 0.4267rem;
+      min-height: auto;
+      .ext-size-base {
+        font-weight: bold;
+        font-size: 0.3733rem;
+        color: #000000;
+        line-height: 0.44rem;
+      }
+      .n-anchor {
+        font-weight: 400;
+        font-size: 0.32rem;
+        color: #333333;
+        line-height: 0.3733rem;
+      }
+    }
+  }
+}
 </style>

+ 189 - 140
packages/web/src/pages/swiper.vue

@@ -1,51 +1,80 @@
 <template>
-    <swiper :slides-per-view="grid" :space-between="50" @swiper="onSwiper" @slideChange="onSlideChange">
-        <swiper-slide v-for="child of data" :key="child.id" @click="handleToDoc(child as any as ArticleDetailType)">
-            <div class="cover w-full h-[400px] overflow-hidden b-rd-xl"
-                :style="{ backgroundImage: `url(${child.cover})` }"></div>
-            <div
-                class="w-full h-full flex flex-col absolute top-0 left-0 -mx-auto justify-end overflow-hidden shadow-blueGray">
-                <div
-                    class="w-full h-[50px] title text-black font-size-[20px] flex justify-center items-center bg-white bg-op-50">
-                    {{ child.title }}
-                </div>
-            </div>
-
-        </swiper-slide>
-        <div class=" flex flex-row items-center justify-center m-t-[30px] ">
-            <div class="swiper-button-prev">
-                <!-- <button @click="handlePrev">Prev</button> -->
-                <n-icon :class="{ disable: activeIndex === 0 }" class="icon" size="32" @click="handlePrev"
-                    color="#0661C9">
-                    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
-                        viewBox="0 0 32 32">
-                        <defs></defs>
-                        <path
-                            d="M16 2a14 14 0 1 0 14 14A14 14 0 0 0 16 2zm8 15H11.85l5.58 5.573L16 24l-8-8l8-8l1.43 1.393L11.85 15H24z">
-                        </path>
-                    </svg>
-
-                </n-icon>
-            </div>
-            <div class="swiper-button-next">
-                <n-icon :class="{ disable: activeIndex === (data.length - grid) }" class="icon" size="32"
-                    @click="handleNext" color="#0661C9">
-                    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
-                        viewBox="0 0 32 32">
-                        <defs></defs>
-                        <path
-                            d="M2 16A14 14 0 1 0 16 2A14 14 0 0 0 2 16zm6-1h12.15l-5.58-5.607L16 8l8 8l-8 8l-1.43-1.427L20.15 17H8z">
-                        </path>
-                    </svg>
-                </n-icon>
-                <!-- <button @click="handleNext">next</button> -->
-            </div>
+  <swiper
+    :slides-per-view="browser.isMobile() ? 2.5 : grid"
+    :space-between="browser.isMobile() ? 20 : 50"
+    @swiper="onSwiper"
+    @slideChange="onSlideChange"
+  >
+    <swiper-slide
+      v-for="child of data"
+      :key="child.id"
+      @click="handleToDoc(child as any as ArticleDetailType)"
+    >
+      <div
+        class="cover w-full h-[400px] overflow-hidden b-rd-xl"
+        :style="{ backgroundImage: `url(${child.cover})` }"
+      ></div>
+      <div
+        class="w-full h-full flex flex-col absolute top-0 left-0 -mx-auto justify-end overflow-hidden shadow-blueGray"
+      >
+        <div
+          class="w-full h-[50px] title text-black font-size-[20px] flex justify-center items-center bg-white bg-op-50"
+        >
+          {{ child.title }}
         </div>
-
-    </swiper>
+      </div>
+    </swiper-slide>
+    <div class="flex flex-row items-center justify-center m-t-[30px]">
+      <div class="swiper-button-prev">
+        <!-- <button @click="handlePrev">Prev</button> -->
+        <n-icon
+          :class="{ disable: activeIndex === 0 }"
+          class="icon"
+          size="32"
+          @click="handlePrev"
+          color="#0661C9"
+        >
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            xmlns:xlink="http://www.w3.org/1999/xlink"
+            viewBox="0 0 32 32"
+          >
+            <defs></defs>
+            <path
+              d="M16 2a14 14 0 1 0 14 14A14 14 0 0 0 16 2zm8 15H11.85l5.58 5.573L16 24l-8-8l8-8l1.43 1.393L11.85 15H24z"
+            ></path>
+          </svg>
+        </n-icon>
+      </div>
+      <div class="swiper-button-next">
+        <n-icon
+          :class="{
+            disable: browser.isMobile()
+              ? activeIndex >= data.length - grid
+              : activeIndex === data.length - grid,
+          }"
+          class="icon"
+          size="32"
+          @click="handleNext"
+          color="#0661C9"
+        >
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            xmlns:xlink="http://www.w3.org/1999/xlink"
+            viewBox="0 0 32 32"
+          >
+            <defs></defs>
+            <path
+              d="M2 16A14 14 0 1 0 16 2A14 14 0 0 0 2 16zm6-1h12.15l-5.58-5.607L16 8l8 8l-8 8l-1.43-1.427L20.15 17H8z"
+            ></path>
+          </svg>
+        </n-icon>
+        <!-- <button @click="handleNext">next</button> -->
+      </div>
+    </div>
+  </swiper>
 </template>
 
-
 <script setup lang="ts">
 import { NH1, NH5, NIcon, NGrid, NGi } from 'naive-ui'
 import { getMenuList, type ArticleDetailType } from '@/api'
@@ -53,137 +82,157 @@ import type { MenuItem } from '@/api'
 import { Swiper, SwiperSlide } from 'swiper/vue'
 import 'swiper/css'
 import router from '@/plugins/router.ts'
+import browser from '@/utils/browser'
 const { t } = useI18n()
 
-defineProps({
-    data: [],
-    grid: {
-        type: Number,
-        default: 3
-    }
+const props = defineProps({
+  data: [],
+  grid: {
+    type: Number,
+    default: 3,
+  },
 })
-const swiperInstance = ref();
+
+const swiperInstance = ref()
 const activeIndex = ref(0)
 
 const onSwiper = (swiper: unknown) => {
-    if (!swiperInstance.value) {
-        console.log(swiper)
-        swiperInstance.value = swiper
-    }
+  if (!swiperInstance.value) {
+    console.log(swiper)
+    swiperInstance.value = swiper
+  }
 }
 
-
 const handleToDoc = (child: ArticleDetailType) => {
-    const { articleId, categoryId } = child
-    console.log("handleToDoc", child)
-    if (articleId) {
-        router.push({ path: `/showdoc/${articleId}` })
-    } else {
-        router.push({ path: `/showcate/${categoryId}` })
-    }
+  const { articleId, categoryId } = child
+  console.log('handleToDoc', child)
+  if (articleId) {
+    router.push({ path: `/showdoc/${articleId}` })
+  } else {
+    router.push({ path: `/showcate/${categoryId}` })
+  }
 }
 const handleToMore = (item: ArticleDetailType) => {
-    if (item && item.children && item.children.length > 0) {
-        const cateId = item.children[0].categoryId;
-        console.log('cateId', cateId)
-        router.push({ path: `/showcate/${cateId}` })
-    }
+  if (item && item.children && item.children.length > 0) {
+    const cateId = item.children[0].categoryId
+    console.log('cateId', cateId)
+    router.push({ path: `/showcate/${cateId}` })
+  }
 }
 const onSlideChange = (swiper) => {
-    console.log('slide change', swiper);
-    activeIndex.value = swiper.activeIndex
-};
+  console.log('slide change', swiper)
+  activeIndex.value = swiper.activeIndex
+}
 const handlePrev = () => {
-    console.log('handlePrev')
-    swiperInstance.value.slidePrev()
+  console.log('handlePrev')
+  swiperInstance.value.slidePrev()
 }
 const handleNext = () => {
-    console.log('handleNext')
-    swiperInstance.value.slideNext()
-
+  console.log('handleNext')
+  swiperInstance.value.slideNext()
 }
 </script>
 
 <style lang="scss" scoped>
 .trans-white-50 {
-    background: rgba(255, 255, 255, 0.5);
+  background: rgba(255, 255, 255, 0.5);
 }
 
 .icon {
-    color: #0661C9;
-    margin: 0 5px;
+  color: #0661c9;
+  margin: 0 5px;
 
-    &.disable {
-        opacity: 0.5;
-        user-select: none;
-    }
+  &.disable {
+    opacity: 0.5;
+    user-select: none;
+  }
 
-    &:hover {
-        cursor: pointer;
-    }
+  &:hover {
+    cursor: pointer;
+  }
 }
 
 .cover {
-    background-repeat: no-repeat;
-    background-position: center top;
-    background-size: cover;
+  background-repeat: no-repeat;
+  background-position: center top;
+  background-size: cover;
 }
 
 .show-item {
-    cursor: pointer;
-    background-repeat: no-repeat;
-    background-position: center bottom;
-    background-size: cover;
+  cursor: pointer;
+  background-repeat: no-repeat;
+  background-position: center bottom;
+  background-size: cover;
+
+  &.style-0 {
+    width: 614px;
+    height: 346px;
+  }
+
+  &.style-2 {
+    width: 610px;
+    height: 104px;
+    background: #f5f9ff;
+    border-radius: 10px 10px 10px 10px;
+  }
+
+  &.style-3 {
+    width: 411px;
+    height: 240px;
+    background: #ffffff;
+    box-shadow: 0px 0px 10px 0px rgba(6, 97, 201, 0.2);
+    border-radius: 10px 10px 10px 10px;
+  }
+
+  &.style-4 {
+    width: 482px;
+    min-height: 200px;
+    background: #f5f9ff;
+    border-radius: 10px 10px 10px 10px;
+    padding: 30px 64px;
+    cursor: default;
+  }
+
+  .otherList {
+    padding: 0;
+
+    li {
+      padding: 5px 0;
+      text-decoration: none;
+      list-style: none;
+
+      &:hover {
+        cursor: pointer;
+      }
 
-    &.style-0 {
-        width: 614px;
-        height: 346px;
-    }
+      &.more {
+        color: #0661c9;
 
-    &.style-2 {
-        width: 610px;
-        height: 104px;
-        background: #f5f9ff;
-        border-radius: 10px 10px 10px 10px;
-    }
-
-    &.style-3 {
-        width: 411px;
-        height: 240px;
-        background: #ffffff;
-        box-shadow: 0px 0px 10px 0px rgba(6, 97, 201, 0.2);
-        border-radius: 10px 10px 10px 10px;
+        &:hover {
+          color: #107bf5;
+        }
+      }
     }
-
-    &.style-4 {
-        width: 482px;
-        min-height: 200px;
-        background: #F5F9FF;
-        border-radius: 10px 10px 10px 10px;
-        padding: 30px 64px;
-        cursor: default;
+  }
+}
+[is-mobile] {
+  .swiper {
+    padding: 0 0.4267rem;
+  }
+  .swiper-slide {
+    height: 4.2667rem;
+    border-radius: 8px 8px 8px 8px;
+    .cover {
+      height: 100%;
+      border-radius: 8px 8px 8px 8px;
     }
-
-    .otherList {
-        padding: 0;
-
-        li {
-            padding: 5px 0;
-            text-decoration: none;
-            list-style: none;
-
-            &:hover {
-                cursor: pointer;
-            }
-
-            &.more {
-                color: #0661C9;
-
-                &:hover {
-                    color: #107bf5;
-                }
-            }
-        }
+    .title {
+      height: 0.8533rem;
+      font-weight: 500;
+      font-size: 0.32rem;
+      color: #333333;
+      line-height: 0.3733rem;
     }
+  }
 }
-</style>
+</style>

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 420 - 0
packages/web/src/utils/browser.ts


+ 1 - 1
packages/web/vite.config.ts

@@ -55,7 +55,7 @@ export default defineConfig(({ mode }) => {
         '/api': {
           target: VITE_PROXY_TARGET,
           changeOrigin: true,
-          rewrite: path => path.replace(/^\/api/, ''),
+          // rewrite: path => path.replace(/^\/api/, ''),
           secure: false,
           // configure: (proxy, options) => {
           //   // 配置此项可在响应头中看到请求的真实地址

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 4157 - 0
packages/web/yarn.lock