Browse Source

feat: resiponsive

Carson 3 weeks ago
parent
commit
3fe39bdcbe

+ 10 - 65
.vitepress/config.mts

@@ -116,6 +116,7 @@ export default defineConfig({
   ignoreDeadLinks: true,
   lastUpdated: true,
   themeConfig: {
+    i18nRouting: false,
     search: {
       provider: "local",
       options: {
@@ -239,6 +240,12 @@ export default defineConfig({
             new URL("../components/CustomNavBar.vue", import.meta.url)
           ),
         },
+        {
+          find: /^.*\/VPHome\.vue$/,
+          replacement: fileURLToPath(
+            new URL("../components/CustomVPHome.vue", import.meta.url)
+          ),
+        },
         {
           find: "@/",
           replacement: fileURLToPath(new URL("../", import.meta.url)),
@@ -251,95 +258,33 @@ export default defineConfig({
       label: "简体中文",
       lang: "zh-CN",
       themeConfig: {
+        i18nRouting: false,
         outline: { label: "本页目录", level: "deep" },
         logo: "/logo.png",
         siteTitle: false,
-        // https://vitepress.dev/reference/default-theme-config
-        // nav: [
-        //   { text: 'Home', link: '/' },
-        //   { text: 'Examples', link: '/markdown-examples' }
-        // ],
-        // sidebar: [
-        //   { text: "关于CocoBlockly X", link: "/docs" },
-        //   { text: "常见问题解答", link: "/docs/常见问题解答" },
-        //   {
-        //     text: "开始使用CocoBlockly X",
-        //     link: "/docs/start-using-cocoblockly-x",
-        //     collapsed: true,
-        //     items: [],
-        //   },
-        //   {
-        //     text: "电子模块基本教学",
-        //     collapsed: true,
-        //     items: [{ text: "todo", link: "/docs" }],
-        //   },
-        // ],
         sidebar: rootSideBar,
-        // socialLinks: [
-        //   { icon: 'github', link: 'https://github.com/vuejs/vitepress' }
-        // ]
       },
     },
     "zh-HK": {
       label: "繁体中文",
       lang: "zh-HK",
       themeConfig: {
+        i18nRouting: false,
         outline: { label: "本頁目錄", level: "deep" },
         logo: "/logo.png",
         siteTitle: false,
-        // https://vitepress.dev/reference/default-theme-config
-        // nav: [
-        //   { text: 'Home', link: '/' },
-        //   { text: 'Examples', link: '/markdown-examples' }
-        // ],
-        // sidebar: [
-        //   { text: "什麽是CocoBlockly X", link: "/zh-HK/docs" },
-        //   { text: "常見問題解答", link: "/zh-HK/docs/faq" },
-        //   {
-        //     text: "開始使用CocoBlockly X",
-        //     link: "/zh-HK/docs/start-using-cocoblockly-x",
-        //   },
-        //   {
-        //     text: "電子模組基本教學",
-        //     collapsed: true,
-        //     items: [{ text: "todo", link: "/zh-HK/docs" }],
-        //   },
-        // ],
         sidebar: zhHKSideBar,
-        // socialLinks: [
-        //   { icon: 'github', link: 'https://github.com/vuejs/vitepress' }
-        // ]
       },
     },
     "en-US": {
       label: "English",
       lang: "en-US",
       themeConfig: {
+        i18nRouting: false,
         outline: { label: "On this page", level: "deep" },
         logo: "/logo.png",
         siteTitle: false,
-        // https://vitepress.dev/reference/default-theme-config
-        // nav: [
-        //   { text: 'Home', link: '/' },
-        //   { text: 'Examples', link: '/markdown-examples' }
-        // ],
-        // sidebar: [
-        //   { text: "什麽是CocoBlockly X", link: "/zh-HK/docs" },
-        //   { text: "常見問題解答", link: "/zh-HK/docs/faq" },
-        //   {
-        //     text: "開始使用CocoBlockly X",
-        //     link: "/zh-HK/docs/start-using-cocoblockly-x",
-        //   },
-        //   {
-        //     text: "電子模組基本教學",
-        //     collapsed: true,
-        //     items: [{ text: "todo", link: "/zh-HK/docs" }],
-        //   },
-        // ],
         sidebar: enUSSideBar,
-        // socialLinks: [
-        //   { icon: 'github', link: 'https://github.com/vuejs/vitepress' }
-        // ]
       },
     },
   },

+ 11 - 0
.vitepress/theme/_screen.scss

@@ -0,0 +1,11 @@
+@mixin breakpoint($point) {
+  @if $point == mobile {
+    @media (max-width: 640px) { @content; }
+  }
+  @else if $point == tablet {
+    @media (max-width: 768px) { @content; }
+  }
+  @else if $point == laptop {
+    @media (max-width: 1024px) { @content; }
+  }
+}

+ 11 - 2
components/CustomLayout.vue

@@ -37,7 +37,7 @@ const docsLink = computed(() => {
 <i18n locale="zh-HK">
 {
   "帮助中心": "幫助中心",
-  "可可智慧教育平台": "可可智慧教育平台"
+  "可可智慧教育平台": "CocoClass"
 }
 </i18n>
 <i18n locale="en-US">
@@ -47,14 +47,23 @@ const docsLink = computed(() => {
 }
 </i18n>
 <style scoped lang="scss">
+@use '@/.vitepress/theme/screen' as *;
+
 .content-before {
+  @include breakpoint(mobile) {
+    font-size: 12px;
+    margin: 0;
+  }
   color: #2e5aa8;
   font-size: 16px;
   font-weight: 600;
   line-height: 24px;
-  margin-left: 14px;
+  margin: 0 14px;
 }
 .content-after {
+  @include breakpoint(mobile) {
+    display: none;
+  }
   color: #2e5aa8;
   padding: 4px 12px;
   display: flex;

+ 6 - 4
components/CustomNavBar.vue

@@ -11,6 +11,7 @@ import VPNavBarSearch from "vitepress/dist/client/theme-default/components/VPNav
 import VPNavBarSocialLinks from "vitepress/dist/client/theme-default/components/VPNavBarSocialLinks.vue";
 import VPNavBarTitle from "vitepress/dist/client/theme-default/components/VPNavBarTitle.vue";
 import VPNavBarTranslations from "vitepress/dist/client/theme-default/components/VPNavBarTranslations.vue";
+import CustomNavBarTranslations from './CustomNavBarTranslations.vue'
 
 defineProps<{
   isScreenOpen: boolean;
@@ -57,16 +58,16 @@ const hasSearch = computed(() => {
             <slot name="nav-bar-content-before" />
             <VPNavBarSearch class="search" :class="{ 'hide-search': !hasSearch }" />
             <VPNavBarMenu class="menu" />
-            <VPNavBarTranslations class="translations" />
+            <CustomNavBarTranslations class="translations" />
             <VPNavBarAppearance class="appearance" />
             <VPNavBarSocialLinks class="social-links" />
-            <VPNavBarExtra class="extra" />
+            <!-- <VPNavBarExtra class="extra" /> -->
             <slot name="nav-bar-content-after" />
-            <VPNavBarHamburger
+            <!-- <VPNavBarHamburger
               class="hamburger"
               :active="isScreenOpen"
               @click="$emit('toggle-screen')"
-            />
+            /> -->
           </div>
         </div>
       </div>
@@ -138,6 +139,7 @@ const hasSearch = computed(() => {
 .container > .title,
 .container > .content {
   pointer-events: none;
+  overflow: hidden;
 }
 
 .container :deep(*) {

+ 41 - 0
components/CustomNavBarTranslations.vue

@@ -0,0 +1,41 @@
+<script lang="ts" setup>
+import VPFlyout from 'vitepress/dist/client/theme-default/components/VPFlyout.vue'
+import VPMenuLink from 'vitepress/dist/client/theme-default/components/VPMenuLink.vue'
+import { useData } from 'vitepress/dist/client/theme-default/composables/data'
+import { useLangs } from 'vitepress/dist/client/theme-default/composables/langs'
+
+const { theme } = useData()
+const { localeLinks, currentLang } = useLangs({ correspondingLink: true })
+</script>
+
+<template>
+  <VPFlyout
+    v-if="localeLinks.length && currentLang.label"
+    class="VPNavBarTranslations"
+    icon="vpi-languages"
+    :label="theme.langMenuLabel || 'Change language'"
+  >
+    <div class="items">
+      <p class="title">{{ currentLang.label }}</p>
+
+      <template v-for="locale in localeLinks" :key="locale.link">
+        <VPMenuLink :item="locale" />
+      </template>
+    </div>
+  </VPFlyout>
+</template>
+
+<style scoped>
+.VPNavBarTranslations {
+  display: flex;
+  align-items: center;
+}
+
+.title {
+  padding: 0 24px 0 12px;
+  line-height: 32px;
+  font-size: 14px;
+  font-weight: 700;
+  color: var(--vp-c-text-1);
+}
+</style>

+ 52 - 0
components/CustomVPHome.vue

@@ -0,0 +1,52 @@
+<script setup lang="ts">
+import VPHomeHero from 'vitepress/dist/client/theme-default/components/VPHomeHero.vue'
+import VPHomeFeatures from 'vitepress/dist/client/theme-default/components/VPHomeFeatures.vue'
+import VPHomeContent from 'vitepress/dist/client/theme-default/components/VPHomeContent.vue'
+import { useData } from "vitepress";
+
+const { frontmatter } = useData()
+</script>
+
+<template>
+  <div class="VPHome">
+    <slot name="home-hero-before" />
+    <VPHomeHero>
+      <template #home-hero-info-before><slot name="home-hero-info-before" /></template>
+      <template #home-hero-info><slot name="home-hero-info" /></template>
+      <template #home-hero-info-after><slot name="home-hero-info-after" /></template>
+      <template #home-hero-actions-after><slot name="home-hero-actions-after" /></template>
+      <template #home-hero-image><slot name="home-hero-image" /></template>
+    </VPHomeHero>
+    <slot name="home-hero-after" />
+
+    <slot name="home-features-before" />
+    <VPHomeFeatures />
+    <slot name="home-features-after" />
+
+    <VPHomeContent v-if="frontmatter.markdownStyles !== false">
+      <Content />
+    </VPHomeContent>
+    <Content v-else />
+  </div>
+</template>
+
+<style scoped>
+.VPHome {
+  margin-bottom: 96px;
+}
+
+@media (max-width: 640px) {
+  .VPHome {
+    background: linear-gradient(to bottom, #E7EEFE 0%, #FAFCFF 800px, #FFFFFF 100%);
+  }
+  .VPHome :deep(.vp-doc) {
+    padding: 0 14px;
+  }
+}
+
+@media (min-width: 768px) {
+  .VPHome {
+    margin-bottom: 128px;
+  }
+}
+</style>

+ 38 - 7
components/HomeCard/HomeCard.vue

@@ -20,6 +20,11 @@ const { title, color } = toRefs(props);
 const styles = computed(() => {
   return _.get(
     {
+      primary: {
+        backgroundColor: "#f5f9ff",
+        borderColor: "#e2eeff",
+        '--card-title-color': '#2B67CA',
+      },
       info: {
         backgroundColor: "#f5f9ff",
         borderColor: "#e2eeff",
@@ -27,6 +32,7 @@ const styles = computed(() => {
       skyblue: {
         backgroundColor: "#f6fdff",
         borderColor: "#e3f8f4",
+        '--card-title-color': '#2AA58B',
       },
       white: {
         borderColor: "#e2eeff",
@@ -43,12 +49,7 @@ const onClick = () => {
 };
 </script>
 <template>
-  <div
-    class="card"
-    :class="{ 'is-link': !!link }"
-    :style="styles"
-    @click="onClick"
-  >
+  <div class="card" :class="{ 'is-link': !!link }" :style="styles" @click="onClick">
     <div v-if="$slots.background" class="background" :style="props.backgroundStyle">
       <slot name="background"> </slot>
     </div>
@@ -64,12 +65,20 @@ const onClick = () => {
 </template>
 
 <style lang="scss" scoped>
-:slotted(:deep(h2)), h2 {
+@use '@/.vitepress/theme/screen' as *;
+
+
+
+:slotted(:deep(h2)),
+h2 {
   border: none;
   margin: 0;
   padding: 0;
 }
+
 .card {
+  width: 100%;
+  height: 100%;
   position: relative;
   display: flex;
   flex-direction: column;
@@ -80,21 +89,28 @@ const onClick = () => {
   background: #f5f9ff;
   border: 1px solid #e2eeff;
   min-height: 240px;
+
   :deep(.card) {
     min-height: 162px;
   }
+
   &.is-link {
     cursor: pointer;
   }
+
   .background {
     position: absolute;
   }
+
   h2 {
+    color: var(--card-title-color);
     display: flex;
     align-items: center;
+
     .prefix {
       margin-right: 8px;
     }
+
     .suffix {
       width: 24px;
       height: 24px;
@@ -102,9 +118,24 @@ const onClick = () => {
       color: #00000042;
     }
   }
+
   .el-row {
     row-gap: 20px;
     align-items: stretch;
   }
 }
+
+@include breakpoint(mobile) {
+  .card {
+    padding: 16px;
+    min-height: 120px;
+    gap: 12px;
+    min-height: 130px;
+
+    :deep(.card) {
+      min-height: 130px;
+      padding: 24px;
+    }
+  }
+}
 </style>

+ 6 - 0
components/HomeCard/HomeCardTitle.vue

@@ -14,6 +14,8 @@ const props = withDefaults(
   </h2>
 </template>
 <style lang="scss" scoped>
+@use '@/.vitepress/theme/screen' as *;
+
 h2 {
   border: none;
   margin: 0;
@@ -21,6 +23,10 @@ h2 {
   display: flex;
   align-items: center;
   gap: 8px;
+  color: var(--card-title-color);
+  @include breakpoint(mobile) {
+    font-size: 18px;
+  }
   .suffix {
     width: 24px;
     height: 24px;

+ 110 - 35
components/HomeContent.vue

@@ -11,6 +11,9 @@ import IconAiZhuShou from "~icons/ccrbi-colored/ai-zhu-shou";
 import IconAiChuangJian from "~icons/ccrbi-colored/ai-chuang-jian";
 import { useData, withBase, useRouter } from "vitepress";
 import path from "path-browserify";
+import { useBreakpoints } from "@vueuse/core";
+import { computed } from 'vue'
+
 
 const { localeIndex } = useData();
 const router = useRouter();
@@ -19,11 +22,96 @@ const withLink = (href: string) => {
     path.join("/", localeIndex.value === "root" ? "" : localeIndex.value, "docs", href)
   );
 };
+const breakpoints = useBreakpoints({
+  tablet: 640,
+  laptop: 1024,
+  desktop: 1280,
+})
+const isMobile = computed(() => breakpoints.isSmaller('tablet'))
+
+const cardNewerbgStyle = computed(() => {
+  if (isMobile.value) {
+    return {
+      left: 0,
+      right: 0,
+      bottom: '8px',
+      margin: 'auto',
+      width: '134px',
+      height: '74px',
+    }
+  }
+  return {
+    right: '16px',
+    bottom: '24px',
+    width: '268px',
+    height: '148px',
+  }
+})
+
+const cardLoginbgStyle = computed(() => {
+  if (isMobile.value) {
+    return {
+      left: 0,
+      right: 0,
+      bottom: '8px',
+      margin: 'auto',
+      width: '64px',
+      height: '64px',
+    }
+  }
+  return {
+    right: '40px',
+    bottom: '24px',
+    width: '128px',
+    height: '128px',
+  }
+})
+
+const cardUsualQuestionbgStyle = computed(() => {
+  if (isMobile.value) {
+    return {
+      right: '24px',
+      top: 0,
+      bottom: 0,
+      margin: 'auto',
+      width: '102px',
+      height: '102px',
+    }
+  }
+  return {
+    right: '10px',
+    bottom: '0',
+    width: '122px',
+    height: '128px',
+  }
+})
+
+const cardAIHowbgStyle = computed(() => {
+  if (isMobile.value) {
+    return {
+      right: '24px',
+      top: 0,
+      bottom: 0,
+      margin: 'auto',
+      width: '133px',
+      height: '74px',
+    }
+  }
+  return {
+    right: '17px',
+    bottom: '0',
+    width: '167px',
+    height: '92px',
+  }
+})
 </script>
 
 <template>
   <div class="home-content">
-    <HomeSection title="您好,需要提供什么帮助?">
+    <HomeSection>
+      <template #title>
+        <h1 :style="{ fontSize: isMobile ? '24px' : undefined }">您好,需要提供什么帮助?</h1>
+      </template>
       <HomeSectionContent>
         <Search />
       </HomeSectionContent>
@@ -31,19 +119,14 @@ const withLink = (href: string) => {
 
     <HomeSection title="新手入门">
       <HomeSectionContent :span="12">
-        <HomeCard color="skyblue" :backgroundStyle="{
-          right: '16px',
-          bottom: '24px',
-          width: '268px',
-          height: '148px',
-        }" :link="withLink('')">
+        <HomeCard color="skyblue" :backgroundStyle="cardNewerbgStyle" :link="withLink('')">
           <template #background>
             <img src="@/assets/images/card1.png" />
           </template>
           <template #title>
             <HomeCard.Title showArrow> 平台概览 </HomeCard.Title>
           </template>
-          <HomeCardContent>
+          <HomeCardContent v-if="!isMobile">
             <ul>
               <li>什么是可可智慧教育平台?</li>
               <li>功能概览</li>
@@ -52,19 +135,14 @@ const withLink = (href: string) => {
         </HomeCard>
       </HomeSectionContent>
       <HomeSectionContent :span="12">
-        <HomeCard :backgroundStyle="{
-          right: '40px',
-          bottom: '24px',
-          width: '128px',
-          height: '128px',
-        }" :link="withLink('#登录')">
+        <HomeCard color="primary" :backgroundStyle="cardLoginbgStyle" :link="withLink('#登录')">
           <template #background>
             <img src="@/assets/images/card2.png" />
           </template>
           <template #title>
             <HomeCard.Title showArrow> 用户登录 </HomeCard.Title>
           </template>
-          <HomeCardContent>
+          <HomeCardContent v-if="!isMobile">
             <ul>
               <li>如何获取账户?</li>
               <li>如何登录?</li>
@@ -77,7 +155,7 @@ const withLink = (href: string) => {
     <HomeSection title="平台使用">
       <HomeSectionContent :span="24">
         <HomeCard title="基础使用">
-          <HomeCardContent :span="8">
+          <HomeCardContent :span="isMobile ? 24 : 8">
             <HomeCard color="white" :link="withLink('课程创建')">
               <template #title>
                 <HomeCard.Title>
@@ -90,7 +168,7 @@ const withLink = (href: string) => {
               </HomeCardContent>
             </HomeCard>
           </HomeCardContent>
-          <HomeCardContent :span="8">
+          <HomeCardContent :span="isMobile ? 24 : 8">
             <HomeCard color="white" :link="withLink('课程授课')">
               <template #title>
                 <HomeCard.Title>
@@ -103,7 +181,7 @@ const withLink = (href: string) => {
               </HomeCardContent>
             </HomeCard>
           </HomeCardContent>
-          <HomeCardContent :span="8">
+          <HomeCardContent :span="isMobile ? 24 : 8">
             <HomeCard color="white" :link="undefined">
               <template #title>
                 <HomeCard.Title>
@@ -134,7 +212,7 @@ const withLink = (href: string) => {
               </HomeCardContent>
             </HomeCard>
           </HomeCardContent> -->
-          <HomeCardContent :span="8">
+          <HomeCardContent :span="isMobile ? 24 : 8">
             <HomeCard color="white" :link="withLink('教师管理')">
               <template #title>
                 <HomeCard.Title>
@@ -147,7 +225,7 @@ const withLink = (href: string) => {
               </HomeCardContent>
             </HomeCard>
           </HomeCardContent>
-          <HomeCardContent :span="8">
+          <HomeCardContent :span="isMobile ? 24 : 8">
             <HomeCard color="white" :link="withLink('AI工具')">
               <template #title>
                 <HomeCard.Title>
@@ -158,7 +236,7 @@ const withLink = (href: string) => {
               <HomeCardContent> 使用生成式人工智能技术提供对话式工具。 </HomeCardContent>
             </HomeCard>
           </HomeCardContent>
-          <HomeCardContent :span="8">
+          <HomeCardContent :span="isMobile ? 24 : 8">
             <HomeCard color="white" :link="withLink('综合看板')">
               <template #title>
                 <HomeCard.Title>
@@ -248,13 +326,8 @@ const withLink = (href: string) => {
 
       <HomeSectionContent>
         <HomeCard>
-          <HomeCardContent :span="12">
-            <HomeCard title="常见问题" color="white" :backgroundStyle="{
-              right: '10px',
-              bottom: '0',
-              width: '122px',
-              height: '128px',
-            }" :link="undefined">
+          <HomeCardContent :span="isMobile ? 24 : 12">
+            <HomeCard title="常见问题" color="white" :backgroundStyle="cardUsualQuestionbgStyle" :link="undefined">
               <template #title>
                 <HomeCard.Title>
                   <el-badge value="即将上线"> 常见问题 </el-badge>
@@ -288,13 +361,8 @@ const withLink = (href: string) => {
               </HomeCardContent>
             </HomeCard>
           </HomeCardContent> -->
-          <HomeCardContent :span="12">
-            <HomeCard title="如何与AI对话" color="white" :backgroundStyle="{
-              right: '17px',
-              bottom: '0',
-              width: '167px',
-              height: '92px',
-            }" :link="undefined">
+          <HomeCardContent :span="isMobile ? 24 : 12">
+            <HomeCard title="如何与AI对话" color="white" :backgroundStyle="cardAIHowbgStyle" :link="undefined">
               <template #title>
                 <HomeCard.Title>
                   <el-badge value="即将上线"> 如何与AI对话 </el-badge>
@@ -315,12 +383,19 @@ const withLink = (href: string) => {
 </template>
 
 <style lang="scss" scoped>
+@use '@/.vitepress/theme/screen' as *;
+
 .home-content {
   display: flex;
   flex-direction: column;
   align-items: center;
   gap: 80px;
   padding-top: 80px;
+
+  @include breakpoint(mobile) {
+    gap: 32px;
+    padding-top: 50px;
+  }
 }
 
 .fancy-list {

+ 26 - 2
components/HomeSection/HomeSection.vue

@@ -1,24 +1,35 @@
 <script setup lang="ts">
 import { toRefs, computed, useSlots, h } from "vue";
+import { useBreakpoints } from "@vueuse/core";
+
 const props = defineProps<{
   title?: string;
 }>();
 const { title } = toRefs(props);
 
+const breakpoints = useBreakpoints({
+  tablet: 640,
+  laptop: 1024,
+  desktop: 1280,
+})
+const isMobile = computed(() => breakpoints.isSmaller('tablet'))
+
 const slots = useSlots();
 const titleNode = computed(() => {
-  return slots.title ? slots.title() : title.value ? h("h1", {}, title.value) : null;
+  return slots.title ? slots.title()?.[0] : title.value ? h("h1", {}, title.value) : null;
 });
 </script>
 <template>
   <div class="home-section">
     <component :is="titleNode" class="title" />
-    <el-row class="content" justify="center" :gutter="20" tag="section" v-bind="$attrs">
+    <el-row class="content" justify="center" :gutter="isMobile ? 8 : 20" tag="section" v-bind="$attrs">
       <slot></slot>
     </el-row>
   </div>
 </template>
 <style lang="scss" scoped>
+@use '@/.vitepress/theme/screen' as *;
+
 .home-section {
   display: flex;
   flex-direction: column;
@@ -26,9 +37,22 @@ const titleNode = computed(() => {
   width: 100%;
   gap: 20px;
 }
+
+.title {
+  text-wrap: nowrap;
+
+  @include breakpoint(mobile) {
+    width: 100%;
+    padding-left: 10px;
+    font-size: 16px;
+    line-height: 16px;
+  }
+}
+
 .content {
   width: 100%;
 }
+
 .el-row {
   row-gap: 20px;
   align-items: stretch;

+ 2 - 1
components/Search/index.vue

@@ -226,7 +226,7 @@ const searchRecommend = (e) => {
 </i18n>
 <style lang="scss" scoped>
 .search-container {
-  width: 514px;
+  max-width: 514px;
   margin: auto;
   position: relative;
   .search-trigger {
@@ -235,6 +235,7 @@ const searchRecommend = (e) => {
     width: 100%;
     height: 52px;
     border-radius: 26px;
+    background: #fff;
     display: flex;
     align-items: center;
     padding: 0 10px;