chenlei 1 ano atrás
pai
commit
4a81968f62
47 arquivos alterados com 1571 adições e 93 exclusões
  1. 4 0
      components.d.ts
  2. 1 0
      package.json
  3. 31 3
      pnpm-lock.yaml
  4. 2 0
      src/App.vue
  5. 3 3
      src/assets/css/ariaGlobalStyle.scss
  6. 1 0
      src/assets/svgs/download.svg
  7. 13 7
      src/components/Breadcrumb/index.vue
  8. 92 0
      src/components/FloatDirectory/index.vue
  9. 36 0
      src/components/PageTitle/index.vue
  10. 0 0
      src/components/PageTitle/pLeft.jpg
  11. 2 0
      src/components/Pagination/index.vue
  12. 44 1
      src/router/index.ts
  13. 9 4
      src/views/Collections/index.vue
  14. 144 0
      src/views/Exhibitions/Detail/components/Galleries.vue
  15. 138 0
      src/views/Exhibitions/Detail/components/Objects.vue
  16. 119 0
      src/views/Exhibitions/Detail/components/Overview.vue
  17. 56 0
      src/views/Exhibitions/Detail/index.scss
  18. 92 0
      src/views/Exhibitions/Detail/index.vue
  19. 6 1
      src/views/Exhibitions/components/ListItem/index.vue
  20. 27 0
      src/views/Exhibitions/constants.ts
  21. 0 22
      src/views/Exhibitions/index.scss
  22. 12 35
      src/views/Exhibitions/index.vue
  23. 57 0
      src/views/LearnEngage/Detail/index.scss
  24. 39 0
      src/views/LearnEngage/Detail/index.vue
  25. 12 0
      src/views/LearnEngage/List/index.scss
  26. 18 0
      src/views/LearnEngage/List/index.vue
  27. 78 0
      src/views/LearnEngage/components/Item.vue
  28. 45 0
      src/views/LearnEngage/index.scss
  29. 70 13
      src/views/LearnEngage/index.vue
  30. 3 0
      src/views/Publications/Catalogues/index.vue
  31. 41 0
      src/views/Publications/Detail/index.scss
  32. 21 0
      src/views/Publications/Detail/index.vue
  33. 116 0
      src/views/Publications/Magazines/index.scss
  34. 76 0
      src/views/Publications/Magazines/index.vue
  35. 36 0
      src/views/Publications/constants.ts
  36. BIN
      src/views/Publications/images/1.png
  37. BIN
      src/views/Publications/images/2.png
  38. BIN
      src/views/Publications/images/ac.jpg
  39. BIN
      src/views/Publications/images/banner.jpg
  40. BIN
      src/views/Publications/images/bg_20.png
  41. BIN
      src/views/Publications/images/infoBan.jpg
  42. BIN
      src/views/Publications/images/p1.png
  43. BIN
      src/views/Publications/images/p2.png
  44. BIN
      src/views/Publications/images/p3.png
  45. 51 0
      src/views/Publications/index.scss
  46. 70 0
      src/views/Publications/index.vue
  47. 6 4
      src/views/Visit/index.vue

+ 4 - 0
components.d.ts

@@ -15,13 +15,17 @@ declare module 'vue' {
     ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
     ElDialog: typeof import('element-plus/es')['ElDialog']
     ElImage: typeof import('element-plus/es')['ElImage']
+    ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
     ElPagination: typeof import('element-plus/es')['ElPagination']
+    FloatDirectory: typeof import('./src/components/FloatDirectory/index.vue')['default']
     Layout: typeof import('./src/components/Layout/index.vue')['default']
+    PageTitle: typeof import('./src/components/PageTitle/index.vue')['default']
     Pagination: typeof import('./src/components/Pagination/index.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
     VanIcon: typeof import('vant/es')['Icon']
+    VanSticky: typeof import('vant/es')['Sticky']
     VanSwipe: typeof import('vant/es')['Swipe']
     VanSwipeItem: typeof import('vant/es')['SwipeItem']
     VanSwitch: typeof import('vant/es')['Switch']

+ 1 - 0
package.json

@@ -13,6 +13,7 @@
   "dependencies": {
     "@dage/utils": "^1.0.2",
     "@vue/shared": "^3.4.27",
+    "@vueuse/core": "^10.11.0",
     "element-plus": "^2.7.3",
     "lodash-unified": "^1.0.3",
     "pinia": "^2.1.7",

+ 31 - 3
pnpm-lock.yaml

@@ -11,6 +11,9 @@ dependencies:
   '@vue/shared':
     specifier: ^3.4.27
     version: 3.4.27
+  '@vueuse/core':
+    specifier: ^10.11.0
+    version: 10.11.0(vue@3.4.27)
   element-plus:
     specifier: ^2.7.3
     version: 2.7.3(vue@3.4.27)
@@ -63,7 +66,7 @@ devDependencies:
     version: 5.4.5
   unplugin-auto-import:
     specifier: ^0.17.6
-    version: 0.17.6
+    version: 0.17.6(@vueuse/core@10.11.0)
   unplugin-vue-components:
     specifier: ^0.27.0
     version: 0.27.0(vue@3.4.27)
@@ -892,6 +895,9 @@ packages:
     resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
     dev: false
 
+  /@types/web-bluetooth@0.0.20:
+    resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
+
   /@vant/popperjs@1.3.0:
     resolution: {integrity: sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==}
     dev: false
@@ -1081,6 +1087,17 @@ packages:
     resolution: {integrity: sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==}
     dev: true
 
+  /@vueuse/core@10.11.0(vue@3.4.27):
+    resolution: {integrity: sha512-x3sD4Mkm7PJ+pcq3HX8PLPBadXCAlSDR/waK87dz0gQE+qJnaaFhc/dZVfJz+IUYzTMVGum2QlR7ImiJQN4s6g==}
+    dependencies:
+      '@types/web-bluetooth': 0.0.20
+      '@vueuse/metadata': 10.11.0
+      '@vueuse/shared': 10.11.0(vue@3.4.27)
+      vue-demi: 0.14.8(vue@3.4.27)
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - vue
+
   /@vueuse/core@8.9.4(vue@3.4.27):
     resolution: {integrity: sha512-B/Mdj9TK1peFyWaPof+Zf/mP9XuGAngaJZBwPaXBvU3aCTZlx3ltlrFFFyMV4iGBwsjSCeUCgZrtkEj9dS2Y3Q==}
     peerDependencies:
@@ -1111,6 +1128,9 @@ packages:
       - vue
     dev: false
 
+  /@vueuse/metadata@10.11.0:
+    resolution: {integrity: sha512-kQX7l6l8dVWNqlqyN3ePW3KmjCQO3ZMgXuBMddIu83CmucrsBfXlH+JoviYyRBws/yLTQO8g3Pbw+bdIoVm4oQ==}
+
   /@vueuse/metadata@8.9.4:
     resolution: {integrity: sha512-IwSfzH80bnJMzqhaapqJl9JRIiyQU0zsRGEgnxN6jhq7992cPUJIRfV+JHRIZXjYqbwt07E1gTEp0R0zPJ1aqw==}
     dev: false
@@ -1119,6 +1139,14 @@ packages:
     resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
     dev: false
 
+  /@vueuse/shared@10.11.0(vue@3.4.27):
+    resolution: {integrity: sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==}
+    dependencies:
+      vue-demi: 0.14.8(vue@3.4.27)
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - vue
+
   /@vueuse/shared@8.9.4(vue@3.4.27):
     resolution: {integrity: sha512-wt+T30c4K6dGRMVqPddexEVLa28YwxW5OFIPmzUHICjphfAuBFTTdDoyqREZNDOFJZ44ARH1WWQNCUK8koJ+Ag==}
     peerDependencies:
@@ -3502,7 +3530,7 @@ packages:
     engines: {node: '>= 10.0.0'}
     dev: true
 
-  /unplugin-auto-import@0.17.6:
+  /unplugin-auto-import@0.17.6(@vueuse/core@10.11.0):
     resolution: {integrity: sha512-dmX0Pex5DzMzVuALkexboOZvh51fL/BD6aoPO7qHoTYGlQp0GRKsREv2KMF1lzYI9SXKQiRxAjwzbQnrFFNydQ==}
     engines: {node: '>=14'}
     peerDependencies:
@@ -3516,6 +3544,7 @@ packages:
     dependencies:
       '@antfu/utils': 0.7.8
       '@rollup/pluginutils': 5.1.0
+      '@vueuse/core': 10.11.0(vue@3.4.27)
       fast-glob: 3.3.2
       local-pkg: 0.5.0
       magic-string: 0.30.10
@@ -3682,7 +3711,6 @@ packages:
         optional: true
     dependencies:
       vue: 3.4.27(typescript@5.4.5)
-    dev: false
 
   /vue-router@4.3.2(vue@3.4.27):
     resolution: {integrity: sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==}

+ 2 - 0
src/App.vue

@@ -35,8 +35,10 @@ body.aria-active {
 }
 
 .container {
+  position: relative;
   margin: 0 auto;
   width: 100%;
   max-width: 1200px;
+  color: var(--black-text-color);
 }
 </style>

+ 3 - 3
src/assets/css/ariaGlobalStyle.scss

@@ -174,15 +174,15 @@ code {
 }
 
 body.aria-active {
-  margin-top: @accessibility-menu-height;
+  margin-top: $accessibility-menu-height;
 }
 
 body.aria-active.aria-magnifying {
-  margin-bottom: @magnify-area-height;
+  margin-bottom: $magnify-area-height;
 }
 
 html.aria-active {
-  scroll-padding-top: @accessibility-menu-height;
+  scroll-padding-top: $accessibility-menu-height;
 }
 
 // 对于有data-aria-xxx-area attribute的元素,鼠标点击(或者点击其不可focus的子元素)时不应该导致focus到这个元素上,换言之,这种元素只应该能通过tab键focus。

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
src/assets/svgs/download.svg


+ 13 - 7
src/components/Breadcrumb/index.vue

@@ -20,12 +20,14 @@
       Home>
     </RouterLink>
     <RouterLink
+      v-for="item in parents"
+      :key="item.label"
       replace
       tabindex="0"
-      :to="{ name: parent.pathName }"
-      :aria-description="parent.label"
+      :to="item.routeParams"
+      :aria-description="item.label"
     >
-      {{ parent.label }}>
+      {{ item.label }}>
     </RouterLink>
     <span tabindex="0" :aria-description="curRoute?.name">
       {{ curRoute?.name }}
@@ -34,11 +36,13 @@
 </template>
 
 <script lang="ts" setup>
+import type { RouteLocationRaw } from "vue-router";
+
 defineProps<{
-  parent: {
+  parents: {
     label: string;
-    pathName: string;
-  };
+    routeParams: RouteLocationRaw;
+  }[];
 
   curRoute?: {
     name: string;
@@ -53,6 +57,8 @@ defineProps<{
   height: 28px;
   line-height: 28px;
   font-size: 12px;
-  margin: 10px auto;
+  margin: 0 auto;
+  padding: 10px 0;
+  box-sizing: content-box;
 }
 </style>

+ 92 - 0
src/components/FloatDirectory/index.vue

@@ -0,0 +1,92 @@
+<template>
+  <VanSticky :offset-top="100" @scroll="handleScroll">
+    <ul class="float-directory">
+      <li
+        v-for="(item, idx) in list"
+        :key="idx"
+        :class="{ active: curActiveIndex === idx }"
+        tabindex="0"
+        aria-label="Link"
+        :aria-description="item.label"
+        @click="handleClick(item.offsetTop)"
+      >
+        {{ item.label }}
+      </li>
+    </ul>
+  </VanSticky>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref } from "vue";
+
+const props = defineProps<{
+  list: {
+    label: string;
+    offsetTop: number;
+  }[];
+}>();
+
+const scrollTop = ref(0);
+
+const curActiveIndex = computed(() => {
+  for (let i = 0; i < props.list.length; i++) {
+    const nextSection = props.list[i + 1];
+
+    if (scrollTop.value >= props.list[i].offsetTop) {
+      if (nextSection && scrollTop.value < nextSection.offsetTop) {
+        return i;
+      }
+
+      if (i === props.list.length - 1) return i;
+    }
+  }
+
+  return 0;
+});
+
+const handleClick = (offsetTop: number) => {
+  window.scrollTo({ top: offsetTop, behavior: "smooth" });
+};
+
+const handleScroll = (e: any) => {
+  scrollTop.value = e.scrollTop;
+};
+</script>
+
+<style lang="scss" scoped>
+.float-directory {
+  position: absolute;
+  top: 20px;
+  right: 20px;
+  z-index: 998;
+
+  li {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    margin-bottom: 1px;
+    padding: 0 25px 0 25px;
+    color: #fff;
+    line-height: 50px;
+    font-size: 14px;
+    background: #333;
+    cursor: pointer;
+
+    &.active {
+      background: var(--van-primary-color);
+
+      &::before {
+        background: white;
+      }
+    }
+    &::before {
+      content: "";
+      display: block;
+      width: 8px;
+      height: 8px;
+      border: 1px solid white;
+      border-radius: 50%;
+    }
+  }
+}
+</style>

+ 36 - 0
src/components/PageTitle/index.vue

@@ -0,0 +1,36 @@
+<template>
+  <div class="page-title">
+    <img src="./pLeft.jpg" />
+    <span tabindex="1" :aria-description="title">{{ title }}</span>
+  </div>
+</template>
+
+<script lang="ts" setup>
+defineProps<{
+  title?: string;
+}>();
+</script>
+
+<style lang="scss" scoped>
+.page-title {
+  position: relative;
+  display: flex;
+  margin-bottom: 20px;
+  height: 65px;
+  align-items: center;
+  font-size: 24px;
+  font-weight: bold;
+  text-indent: 5px;
+  border-bottom: 1px solid var(--black-text-color);
+
+  &::after {
+    content: "";
+    position: absolute;
+    bottom: -1px;
+    left: 0;
+    width: 80px;
+    height: 2px;
+    background: var(--van-primary-color);
+  }
+}
+</style>

src/assets/images/Visit/pLeft.jpg → src/components/PageTitle/pLeft.jpg


+ 2 - 0
src/components/Pagination/index.vue

@@ -10,6 +10,8 @@ const props = defineProps<Partial<PaginationProps>>();
 
 <style lang="scss">
 .pagination {
+  --el-pagination-bg-color: transparent;
+  --el-pagination-button-disabled-bg-color: transparent;
   --el-pagination-font-size: 16px;
 
   .el-pager {

+ 44 - 1
src/router/index.ts

@@ -69,14 +69,57 @@ const router = createRouter({
           component: () => import("../views/Exhibitions/index.vue"),
         },
         {
+          path: "/Layout/ExhibitionsInfo",
+          name: "ExhibitionsDetail",
+          component: () => import("../views/Exhibitions/Detail/index.vue"),
+        },
+        {
           path: "/Layout/Collections/:type",
           name: "Collections",
           component: () => import("../views/Collections/index.vue"),
         },
         {
-          path: "/Layout/LearnEngage/:type",
+          path: "/Layout/LearnEngage",
           name: "LearnEngage",
+          redirect: "/Layout/LearnEngage/Students",
           component: () => import("../views/LearnEngage/index.vue"),
+          children: [
+            {
+              path: "/Layout/LearnEngage/:type",
+              name: "LearnEngageList",
+              component: () => import("../views/LearnEngage/List/index.vue"),
+            },
+            {
+              path: "/Layout/LearnEngageInfo",
+              name: "LearnEngageDetail",
+              component: () => import("../views/LearnEngage/Detail/index.vue"),
+            },
+          ],
+        },
+        {
+          path: "/Layout/Publications",
+          name: "Publications",
+          redirect: "/Layout/Publications/1",
+          component: () => import("../views/Publications/index.vue"),
+          children: [
+            {
+              path: "/Layout/Publications/1",
+              name: "PublicationsMagazines",
+              component: () =>
+                import("../views/Publications/Magazines/index.vue"),
+            },
+            {
+              path: "/Layout/Publications/2",
+              name: "PublicationsCatalogues",
+              component: () =>
+                import("../views/Publications/Catalogues/index.vue"),
+            },
+            {
+              path: "/Layout/PublicationsInfo",
+              name: "PublicationsDetail",
+              component: () => import("../views/Publications/Detail/index.vue"),
+            },
+          ],
         },
       ],
     },

+ 9 - 4
src/views/Collections/index.vue

@@ -9,10 +9,15 @@
 
     <div class="container">
       <Breadcrumb
-        :parent="{
-          label: 'Collections',
-          pathName: 'Collections',
-        }"
+        :parents="[
+          {
+            label: 'Collections',
+            routeParams: {
+              name: 'Collections',
+              params: { type: NAV_LIST[0].type },
+            },
+          },
+        ]"
         :cur-route="curRoute"
       />
 

+ 144 - 0
src/views/Exhibitions/Detail/components/Galleries.vue

@@ -0,0 +1,144 @@
+<template>
+  <div ref="wrapRef" class="exh-detail-galleries">
+    <p class="exh-detail__title">
+      <span>Exhibition Galleries</span>
+    </p>
+
+    <div class="exh-detail-galleries__list">
+      <div v-for="key in 5" :key="key" class="exh-detail-galleries-item">
+        <ElImage
+          fit="cover"
+          src="https://en.capitalmuseum.org.cn/data/Exhibitions/Past/objects39/objects1.jpg"
+        />
+
+        <div class="exh-detail-galleries-item__mask">
+          <div>
+            <SvgIcon
+              name="download"
+              color="var(--white-text-color)"
+              style="width: 26px; height: 26px"
+            />
+            <a href="" download tabindex="0"> Download </a>
+          </div>
+          <div @click="handlePreview">
+            <SvgIcon
+              name="search"
+              color="var(--white-text-color)"
+              style="width: 22px; height: 22px"
+            />
+            <span tabindex="0" aria-label="Button"> Enlarge </span>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="exh-detail-galleries__more">
+      <span tabindex="0" aria-label="Button">
+        {{ showMore ? "Hide" : "See More" }}
+      </span>
+    </div>
+  </div>
+
+  <!-- 点击预览大图 -->
+  <ElImageViewer
+    v-if="showViewer"
+    :url-list="previewSrcList"
+    @close="closeViewer"
+  />
+</template>
+
+<script lang="ts" setup>
+import { ref } from "vue";
+
+const wrapRef = ref();
+const showMore = ref(false);
+const showViewer = ref(false);
+const previewSrcList = ref<string[]>([]);
+
+const closeViewer = () => {
+  showViewer.value = false;
+
+  document.body.style.overflow = "auto";
+};
+
+const handlePreview = () => {
+  previewSrcList.value = [
+    "https://en.capitalmuseum.org.cn/data/Exhibitions/Past/objects39/objects1.jpg",
+  ];
+  showViewer.value = true;
+
+  document.body.style.overflow = "hidden";
+};
+
+defineExpose({
+  wrapRef,
+});
+</script>
+
+<style lang="scss" scoped>
+.exh-detail-galleries {
+  margin: 40px 0;
+  padding: 30px 0;
+  background: var(--white-bg);
+  border: 1px solid #e0e0e0;
+
+  &__list {
+    display: flex;
+    flex-wrap: wrap;
+    margin: 28px -2px -2px;
+
+    .el-image {
+      width: 100%;
+      height: 100%;
+    }
+  }
+  &-item {
+    position: relative;
+    margin: 2px;
+    width: 390px;
+    height: 260px;
+    cursor: pointer;
+
+    &:hover {
+      .exh-detail-galleries-item__mask {
+        display: flex;
+      }
+    }
+    &__mask {
+      display: none;
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      flex-direction: column;
+      align-items: center;
+      gap: 40px;
+      justify-content: center;
+      background-color: var(--topnav-bg-color);
+      text-align: center;
+
+      a,
+      span {
+        font-size: 18px;
+        color: var(--white-text-color);
+      }
+      > div {
+        display: flex;
+        align-items: center;
+        gap: 5px;
+      }
+    }
+  }
+  &__more {
+    font-size: 16px;
+    color: var(--van-primary-color);
+    text-align: center;
+    padding-top: 30px;
+
+    span {
+      cursor: pointer;
+    }
+  }
+}
+</style>

+ 138 - 0
src/views/Exhibitions/Detail/components/Objects.vue

@@ -0,0 +1,138 @@
+<template>
+  <div ref="wrapRef" class="exh-detail-object">
+    <p class="exh-detail__title">
+      <span>Exhibition Objects</span>
+    </p>
+
+    <div class="exh-detail-object__list">
+      <div v-for="key in 5" :key="key" class="exh-detail-object-item">
+        <ElImage
+          src="https://en.capitalmuseum.org.cn/data/Exhibitions/Past/objects39/objects1.jpg"
+        />
+
+        <div class="exh-detail-object-item__mask">
+          <div>
+            <SvgIcon
+              name="download"
+              color="var(--white-text-color)"
+              style="width: 26px; height: 26px"
+            />
+            <a href="" download tabindex="0"> Download </a>
+          </div>
+          <div @click="handlePreview">
+            <SvgIcon
+              name="search"
+              color="var(--white-text-color)"
+              style="width: 22px; height: 22px"
+            />
+            <span tabindex="0" aria-label="Button"> Enlarge </span>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="exh-detail-object__more">
+      <span tabindex="0" aria-label="Button">
+        {{ showMore ? "Hide" : "See More" }}
+      </span>
+    </div>
+  </div>
+
+  <!-- 点击预览大图 -->
+  <ElImageViewer
+    v-if="showViewer"
+    :url-list="previewSrcList"
+    @close="closeViewer"
+  />
+</template>
+
+<script lang="ts" setup>
+import { ref } from "vue";
+
+const showMore = ref(false);
+const showViewer = ref(false);
+const previewSrcList = ref<string[]>([]);
+const wrapRef = ref();
+
+const closeViewer = () => {
+  showViewer.value = false;
+
+  document.body.style.overflow = "auto";
+};
+
+const handlePreview = () => {
+  previewSrcList.value = [
+    "https://en.capitalmuseum.org.cn/data/Exhibitions/Past/objects39/objects1.jpg",
+  ];
+  showViewer.value = true;
+
+  document.body.style.overflow = "hidden";
+};
+
+defineExpose({
+  wrapRef,
+});
+</script>
+
+<style lang="scss" scoped>
+.exh-detail-object {
+  margin: 40px 0;
+  padding: 30px 0;
+  background: var(--white-bg);
+  border: 1px solid #e0e0e0;
+
+  &__list {
+    display: flex;
+    flex-wrap: wrap;
+    margin: 24px -6px -6px;
+  }
+  &-item {
+    position: relative;
+    margin: 6px;
+    width: 226px;
+    height: 410px;
+    cursor: pointer;
+
+    &:hover {
+      .exh-detail-object-item__mask {
+        display: flex;
+      }
+    }
+    &__mask {
+      display: none;
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      flex-direction: column;
+      align-items: center;
+      gap: 40px;
+      justify-content: center;
+      background-color: var(--topnav-bg-color);
+      text-align: center;
+
+      a,
+      span {
+        font-size: 18px;
+        color: var(--white-text-color);
+      }
+      > div {
+        display: flex;
+        align-items: center;
+        gap: 5px;
+      }
+    }
+  }
+  &__more {
+    font-size: 16px;
+    color: var(--van-primary-color);
+    text-align: center;
+    padding-top: 30px;
+
+    span {
+      cursor: pointer;
+    }
+  }
+}
+</style>

+ 119 - 0
src/views/Exhibitions/Detail/components/Overview.vue

@@ -0,0 +1,119 @@
+<template>
+  <div ref="wrapRef" class="exh-detail-overview">
+    <div
+      class="exh-detail-overview-hd"
+      data-aria-viewport-area
+      tabindex="0"
+      aria-description="You've reached the section of exhibition title, please use the tab key to navigate through the content."
+    >
+      <p class="title">Splendid Central Axis of Beijing</p>
+      <div>
+        <div>
+          <img :src="DateIcon" style="width: 18px; height: 20px" />
+          <p style="padding: 0 15px">January 20 - July 23, 2023</p>
+        </div>
+        <div>
+          <img :src="ClockIcon" style="width: 20px; height: 20px" />
+          <p style="padding: 0 15px">
+            Tuesday to Sunday (closed on Mondays) 9:00 a.m. to 5:00 p.m
+          </p>
+        </div>
+        <div>
+          <img :src="AddressIcon" style="width: 14px; height: 20px" />
+          <p style="padding-left: 15px">Room B, F1</p>
+        </div>
+      </div>
+    </div>
+    <div
+      class="exh-detail-overview-main"
+      data-aria-viewport-area
+      tabindex="0"
+      aria-description="You've reached the section of exhibition overview, please use the tab key to go through the content."
+    >
+      <p class="exh-detail__title">
+        <span>Exhibition Overview</span>
+      </p>
+    </div>
+
+    <div class="exh-detail-overview__more">
+      <div tabindex="0" aria-label="Button">Click here to see more</div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import DateIcon from "@/assets/images/bg_5.png";
+import ClockIcon from "@/assets/images/bg_6.png";
+import AddressIcon from "@/assets/images/bg_7.png";
+import { ref } from "vue";
+
+const wrapRef = ref();
+
+defineExpose({
+  wrapRef,
+});
+</script>
+
+<style lang="scss" scoped>
+.exh-detail {
+  &-overview {
+    position: relative;
+    margin-bottom: 40px;
+    background: var(--white-bg);
+    border: 1px solid #e0e0e0;
+
+    &-hd {
+      padding: 40px 210px 20px 40px;
+      border-bottom: 1px solid #e0e0e0;
+
+      .title {
+        margin-bottom: 10px;
+        font-size: 30px;
+        line-height: 44px;
+        font-weight: bold;
+      }
+      > div {
+        display: flex;
+        flex-wrap: wrap;
+        align-items: center;
+        font-size: 14px;
+        line-height: 36px;
+        color: var(--gray-text-color);
+        width: 940px;
+
+        > div {
+          display: flex;
+          align-items: center;
+        }
+      }
+    }
+    &-main {
+      padding: 30px 40px;
+    }
+    &__more {
+      position: absolute;
+      bottom: 0;
+      left: 0;
+      width: 100%;
+      height: 100px;
+      z-index: 98;
+
+      &::before {
+        content: "";
+        display: block;
+        height: 50px;
+        background-image: linear-gradient(hsla(0, 0%, 100%, 0.4), #fff);
+      }
+      div {
+        cursor: pointer;
+        height: 50px;
+        line-height: 50px;
+        font-size: 20px;
+        color: var(--van-primary-color);
+        text-align: center;
+        background: var(--white-bg);
+      }
+    }
+  }
+}
+</style>

+ 56 - 0
src/views/Exhibitions/Detail/index.scss

@@ -0,0 +1,56 @@
+.exh-detail {
+  &-banner {
+    margin-top: -60px;
+    display: block;
+    width: 100%;
+    height: 300px;
+    object-fit: cover;
+  }
+  &-breadcrumb {
+    padding-bottom: 8px;
+    background: url("@/assets/images/Visit/bg_3.png") left bottom repeat-x
+      #f1f1f1;
+  }
+  .container {
+    margin-top: 35px;
+    width: 1180px;
+  }
+  &__title {
+    position: relative;
+    text-align: center;
+
+    &::after {
+      content: "";
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      width: 320px;
+      height: 1px;
+      background: var(--gray-text-color);
+      transform: translate(-50%, -50%);
+    }
+    span {
+      position: relative;
+      padding: 0 20px;
+      font-weight: 700;
+      font-size: 24px;
+      color: var(--van-primary-color);
+      text-align: center;
+      background: var(--white-bg);
+      z-index: 1;
+    }
+  }
+  &__top-btn {
+    display: block;
+    height: 38px;
+    width: 160px;
+    margin: 30px auto;
+    color: var(--black-text-color);
+    border: 1px solid var(--black-text-color);
+    font-size: 16px;
+    line-height: 38px;
+    text-align: center;
+    font-weight: bold;
+    cursor: pointer;
+  }
+}

+ 92 - 0
src/views/Exhibitions/Detail/index.vue

@@ -0,0 +1,92 @@
+<template>
+  <div class="exh-detail">
+    <img
+      class="exh-detail-banner"
+      tabindex="0"
+      data-aria-viewport-area
+      aria-description="You've reached the banner area of the tertiary exhibition page; this area has one image; please use the tab key to go through the content."
+      src="https://en.capitalmuseum.org.cn/data/Exhibitions/Current/infoTop2.jpg"
+    />
+
+    <div class="exh-detail-breadcrumb">
+      <Breadcrumb
+        :parents="[
+        {
+          label: 'Exhibitions',
+          routeParams: {name: 'Exhibitions', params: {type: 1}}
+        },
+        {
+          label: NAV_LIST[0].name,
+          routeParams: {name: 'Exhibitions', params: {type: $route.query.k as string}}
+        },
+      ]"
+      />
+    </div>
+
+    <div ref="containerRef" class="container">
+      <!-- 悬浮目录 -->
+      <FloatDirectory :list="directory" />
+
+      <Overview />
+
+      <Objects ref="objectsRef" />
+
+      <Galleries ref="galleriesRef" />
+    </div>
+
+    <!-- 回到顶部 -->
+    <button
+      class="exh-detail__top-btn"
+      tabindex="0"
+      @click="backTop"
+      @keydown.enter.passive="backTop"
+    >
+      Back to top
+    </button>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref } from "vue";
+import FloatDirectory from "@/components/FloatDirectory/index.vue";
+import Breadcrumb from "@/components/Breadcrumb/index.vue";
+import Overview from "./components/Overview.vue";
+import Objects from "./components/Objects.vue";
+import Galleries from "./components/Galleries.vue";
+import { NAV_LIST } from "../constants";
+
+const containerRef = ref();
+const objectsRef = ref();
+const galleriesRef = ref();
+
+const directory = computed(() => {
+  const containerOffsetTop = containerRef.value?.offsetTop || 0;
+
+  const stack = [
+    {
+      label: "Exhibition Overview",
+      offsetTop: containerOffsetTop,
+    },
+    {
+      label: "Exhibition Objects",
+      offsetTop:
+        (objectsRef.value?.wrapRef?.offsetTop || 0) + containerOffsetTop,
+    },
+    {
+      label: "Exhibition Galleries",
+      offsetTop:
+        (galleriesRef.value?.wrapRef?.offsetTop || 0) + containerOffsetTop,
+    },
+  ];
+
+  return stack;
+});
+
+const backTop = () => {
+  window.scrollTo({ top: 0, behavior: "smooth" });
+};
+</script>
+
+<style lang="scss">
+@import "./index.scss";
+</style>

+ 6 - 1
src/views/Exhibitions/components/ListItem/index.vue

@@ -1,5 +1,10 @@
 <template>
-  <div class="list-item">
+  <div
+    class="list-item"
+    @click="
+      $router.push({ name: 'ExhibitionsDetail', query: { id: 100, k: 1 } })
+    "
+  >
     <img src="" aria-label="Image link" :aria-description="1" />
 
     <div class="list-item__inner" aria-label="Link">

+ 27 - 0
src/views/Exhibitions/constants.ts

@@ -0,0 +1,27 @@
+import CurrentIcon from "@/assets/images/Exhibitions/t_1.png";
+import PermanentIcon from "@/assets/images/Exhibitions/t_2.png";
+import Pastcon from "@/assets/images/Exhibitions/t_3.png";
+import OverseasIcon from "@/assets/images/Exhibitions/t_4.png";
+
+export const NAV_LIST = [
+  {
+    name: "Current Exhibitions",
+    img: CurrentIcon,
+    pathName: "Exhibitions",
+  },
+  {
+    name: "Permanent Exhibitions",
+    img: PermanentIcon,
+    pathName: "Exhibitions",
+  },
+  {
+    name: "Past Exhibitions",
+    img: Pastcon,
+    pathName: "Exhibitions",
+  },
+  {
+    name: "Overseas Exhibitions",
+    img: OverseasIcon,
+    pathName: "Exhibitions",
+  },
+];

+ 0 - 22
src/views/Exhibitions/index.scss

@@ -49,28 +49,6 @@
     margin-top: 0;
   }
 
-  &-title {
-    position: relative;
-    display: flex;
-    margin-bottom: 20px;
-    height: 65px;
-    align-items: center;
-    font-size: 24px;
-    font-weight: bold;
-    text-indent: 5px;
-    border-bottom: 1px solid var(--black-text-color);
-
-    &::after {
-      content: "";
-      position: absolute;
-      bottom: -1px;
-      left: 0;
-      width: 80px;
-      height: 2px;
-      background: var(--van-primary-color);
-    }
-  }
-
   &-filter {
     display: flex;
     align-items: center;

+ 12 - 35
src/views/Exhibitions/index.vue

@@ -38,17 +38,19 @@
 
     <div class="container">
       <Breadcrumb
-        :parent="{
-          label: 'Exhibitions',
-          pathName: 'Exhibitions',
-        }"
+        :parents="[
+          {
+            label: 'Exhibitions',
+            routeParams: {
+              name: 'Exhibitions',
+              params: { type: 1 },
+            },
+          },
+        ]"
         :cur-route="curRoute"
       />
 
-      <div class="exhibitions-title">
-        <img src="@/assets/images/Visit/pLeft.jpg" alt="" />
-        <span tabindex="1">{{ curRoute?.name }}</span>
-      </div>
+      <PageTitle :title="curRoute?.name" />
 
       <div
         class="exhibitions-filter"
@@ -125,10 +127,7 @@
 <script lang="ts" setup>
 import { useRoute } from "vue-router";
 import { computed, ref } from "vue";
-import CurrentIcon from "@/assets/images/Exhibitions/t_1.png";
-import PermanentIcon from "@/assets/images/Exhibitions/t_2.png";
-import Pastcon from "@/assets/images/Exhibitions/t_3.png";
-import OverseasIcon from "@/assets/images/Exhibitions/t_4.png";
+import PageTitle from "@/components/PageTitle/index.vue";
 import ListModeIcon from "@/assets/images/Exhibitions/cut1.png";
 import ActListModeIcon from "@/assets/images/Exhibitions/cut1Ac.png";
 import ImgModeIcon from "@/assets/images/Exhibitions/cut2.png";
@@ -136,35 +135,13 @@ import ActImgModeIcon from "@/assets/images/Exhibitions/cut2Ac.png";
 import Breadcrumb from "@/components/Breadcrumb/index.vue";
 import ListItem from "./components/ListItem/index.vue";
 import ImgItem from "./components/ImgItem/index.vue";
+import { NAV_LIST } from "./constants";
 
 enum MODE {
   LIST = 0,
   IMG = 1,
 }
 
-const NAV_LIST = [
-  {
-    name: "Current Exhibitions",
-    img: CurrentIcon,
-    pathName: "Exhibitions",
-  },
-  {
-    name: "Permanent Exhibitions",
-    img: PermanentIcon,
-    pathName: "Exhibitions",
-  },
-  {
-    name: "Past Exhibitions",
-    img: Pastcon,
-    pathName: "Exhibitions",
-  },
-  {
-    name: "Overseas Exhibitions",
-    img: OverseasIcon,
-    pathName: "Exhibitions",
-  },
-];
-
 const route = useRoute();
 const curRoute = computed(() => NAV_LIST[Number(route.params.type) - 1]);
 

+ 57 - 0
src/views/LearnEngage/Detail/index.scss

@@ -0,0 +1,57 @@
+.learn-detail {
+  display: flex;
+  gap: 30px;
+
+  &-sidebar {
+    width: 260px;
+
+    .el-image {
+      margin-bottom: 10px;
+      width: 100%;
+      height: 180px;
+    }
+    p {
+      position: relative;
+      font-size: 14px;
+      padding: 10px 0 10px 30px;
+      color: var(--gray-text-color);
+    }
+    &__date::before {
+      content: "";
+      position: absolute;
+      top: 10px;
+      left: 0;
+      width: 25px;
+      height: 25px;
+      background: url("@/assets/images/date.png") no-repeat center / contain;
+    }
+    &__address::before {
+      content: "";
+      position: absolute;
+      top: 10px;
+      left: 0;
+      width: 25px;
+      height: 25px;
+      background: url("@/assets/images/address.png") no-repeat center / contain;
+    }
+    &__team::before {
+      content: "";
+      position: absolute;
+      top: 10px;
+      left: 0;
+      width: 25px;
+      height: 25px;
+      background: url("@/assets/images/person.png") no-repeat center / contain;
+    }
+  }
+  &-inner {
+    flex: 1;
+
+    &__title {
+      margin-bottom: 10px;
+      font-size: 24px;
+      font-weight: bold;
+      line-height: 34px;
+    }
+  }
+}

+ 39 - 0
src/views/LearnEngage/Detail/index.vue

@@ -0,0 +1,39 @@
+<template>
+  <div
+    class="learn-detail"
+    tabindex="0"
+    data-aria-viewport-area
+    aria-description="You've reached the content area of the tertiary Learn & Engage page, please use the tab key to navigate through the content."
+  >
+    <div class="learn-detail-sidebar">
+      <ElImage
+        fit="cover"
+        tabindex="0"
+        aria-description="title"
+        alt=""
+        src="http://localhost:8080/data/LearnEngage/in/98.jpg"
+      />
+
+      <p class="learn-detail-sidebar__date" tabindex="0">
+        March 30, 2023 10:00-11:00 am - March 30, 2023 14:00-15:00 pm
+      </p>
+      <p class="learn-detail-sidebar__address" tabindex="0">
+        Limited to 30 people in total (first come, first served)
+      </p>
+      <p class="learn-detail-sidebar__team" tabindex="0">
+        Exhibition of Rare Ancient Jade on the fifth floor of the Round Hall of
+        the Capital Museum
+      </p>
+    </div>
+
+    <div class="learn-detail-inner">
+      <h3 class="learn-detail-inner__title" tabindex="0">
+        Doctors’Day Special Session
+      </h3>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 12 - 0
src/views/LearnEngage/List/index.scss

@@ -0,0 +1,12 @@
+.learn-engage {
+  &-container {
+    display: flex;
+    flex-wrap: wrap;
+    margin: -15px;
+
+    .learn-item {
+      margin: 15px;
+      width: calc(33.3333% - 30px);
+    }
+  }
+}

+ 18 - 0
src/views/LearnEngage/List/index.vue

@@ -0,0 +1,18 @@
+<template>
+  <div class="learn-engage-container">
+    <Item v-for="key in 6" :key="key" />
+  </div>
+
+  <div style="display: flex; justify-content: center; padding: 30px 0">
+    <Pagination :total="50" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import Pagination from "@/components/Pagination/index.vue";
+import Item from "../components/Item.vue";
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 78 - 0
src/views/LearnEngage/components/Item.vue

@@ -0,0 +1,78 @@
+<template>
+  <div
+    class="learn-item"
+    aria-label="Link"
+    @click="
+      $router.push({
+        name: 'LearnEngageDetail',
+        query: { id: 100, type: $route.params.type },
+      })
+    "
+  >
+    <ElImage
+      fit="cover"
+      class="learn-item__img"
+      src="http://localhost:8080/data/LearnEngage/sm/100.png"
+    />
+
+    <div class="learn-item__inner">
+      <p class="learn-item__title limit-line" tabindex="0">
+        Celebration of Spring--taking advantage of the east wind to fly paper
+        kites
+      </p>
+
+      <p class="learn-item__content limit-line" tabindex="0">
+        Venue:Paper Art Space on the first floor of the Capital Museum
+      </p>
+      <p class="learn-item__content limit-line" tabindex="0">
+        Date:April 5, 2023 10:00 am
+      </p>
+      <p class="learn-item__content limit-line" tabindex="0">
+        Limit:Limited to 20 people in total
+      </p>
+    </div>
+
+    <div class="learn-item__more" />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.learn-item {
+  position: relative;
+  cursor: pointer;
+  transition: all 0.3s;
+
+  &:hover {
+    background: var(--white-bg);
+    box-shadow: 0px 0px 5px 4px #ccc;
+  }
+  .el-image {
+    width: 100%;
+    height: 220px;
+  }
+  &__inner {
+    padding: 15px 20px 0;
+    height: 130px;
+  }
+  &__title {
+    margin-bottom: 5px;
+    color: var(--black-text-color);
+    font-weight: 700;
+    font-size: 18px;
+    line-height: 22px;
+  }
+  &__content {
+    font-size: 14px;
+    line-height: 20px;
+    color: var(--gray-text-color);
+  }
+  &__more {
+    position: absolute;
+    right: 8px;
+    bottom: 10px;
+    width: 50px;
+    height: 15.6px;
+    background: url("@/assets/images/MORE.png") no-repeat center / contain;
+  }
+}
+</style>

+ 45 - 0
src/views/LearnEngage/index.scss

@@ -6,4 +6,49 @@
     height: 300px;
     object-fit: cover;
   }
+  &-nav {
+    position: relative;
+    display: flex;
+    justify-content: space-around;
+    margin-top: -15px;
+    padding: 5px 60px;
+    border-radius: 5px;
+    background: var(--white-bg);
+    z-index: 1;
+
+    &__item {
+      padding-top: 8px;
+      width: 168px;
+      height: 108px;
+      text-align: center;
+      color: var(--black-text-color);
+      cursor: pointer;
+
+      &.active {
+        color: var(--van-primary-color);
+
+        .learn-engage-nav__item__icon {
+          background: var(--van-primary-color);
+        }
+      }
+      &__icon {
+        margin: 10px auto;
+        width: 50px;
+        height: 50px;
+        border-radius: 50%;
+        overflow: hidden;
+        background: var(--black-text-color);
+
+        img {
+          width: 100%;
+          height: 100%;
+        }
+      }
+      p {
+        margin-top: 5px;
+        font-size: 14px;
+        line-height: 18px;
+      }
+    }
+  }
 }

+ 70 - 13
src/views/LearnEngage/index.vue

@@ -3,20 +3,37 @@
     <img
       class="learn-engage-banner"
       tabindex="0"
+      data-aria-viewport-area
       aria-description="You've reached the banner area of the Learn & Engage section; this section has one image; please use the tab key to go through the content."
       src="./images/topBan.jpg"
     />
 
     <div class="container">
-      <ul class="visit-nav"></ul>
-
-      <Breadcrumb
-        :parent="{
-          label: 'Learn & Engage',
-          pathName: 'LearnEngage',
-        }"
-        :cur-route="curRoute"
-      />
+      <ul v-if="showNav" class="learn-engage-nav">
+        <li
+          v-for="item in NAV_LIST"
+          :key="item.routeParams.params.type"
+          :class="[
+            'learn-engage-nav__item',
+            {
+              active: $route.params.type === item.routeParams.params.type,
+            },
+          ]"
+          tabindex="0"
+          aria-label="Link"
+          :aria-description="item.name"
+          @click="$router.push(item.routeParams)"
+        >
+          <div class="learn-engage-nav__item__icon">
+            <img :src="item.img" />
+          </div>
+          <p>{{ item.name }}</p>
+        </li>
+      </ul>
+
+      <Breadcrumb :parents="parentRoutes" :cur-route="curRoute" />
+
+      <RouterView />
     </div>
   </div>
 </template>
@@ -33,24 +50,64 @@ const NAV_LIST = [
   {
     name: "For Students",
     img: StudentsIcon,
-    type: "Students",
+    routeParams: {
+      name: "LearnEngageList",
+      params: {
+        type: "Students",
+      },
+    },
   },
   {
     name: "For Adults",
     img: AdultsIcon,
-    type: "Adults",
+    routeParams: {
+      name: "LearnEngageList",
+      params: {
+        type: "Adults",
+      },
+    },
   },
   {
     name: "For Families & Children",
     img: FamiliesIcon,
-    type: "Families",
+    routeParams: {
+      name: "LearnEngageList",
+      params: {
+        type: "Families",
+      },
+    },
   },
 ];
 
 const route = useRoute();
+const parentRoutes = computed(() => {
+  const stack = [
+    {
+      label: "Learn & Engage",
+      routeParams: {
+        name: "LearnEngage",
+      },
+    },
+  ];
+
+  if (route.query.type) {
+    const item = NAV_LIST.find(
+      (i) => i.routeParams.params.type === route.query.type
+    );
+
+    item &&
+      stack.push({
+        label: item.name,
+        routeParams: item.routeParams,
+      });
+  }
+
+  return stack;
+});
 const curRoute = computed(() =>
-  NAV_LIST.find((i) => i.type === (route.params.type as string))
+  NAV_LIST.find((i) => i.routeParams.params.type === route.params.type)
 );
+const showNav = computed(() => route.name === "LearnEngageList");
 </script>
 
 <style lang="scss" scoped>

+ 3 - 0
src/views/Publications/Catalogues/index.vue

@@ -0,0 +1,3 @@
+<template>
+  <div class="magazines"></div>
+</template>

+ 41 - 0
src/views/Publications/Detail/index.scss

@@ -0,0 +1,41 @@
+.publications-detail {
+  &__hd {
+    position: relative;
+    margin-bottom: 23px;
+    color: white;
+    height: 329px;
+
+    > div {
+      position: absolute;
+      left: 50%;
+      top: 0;
+      width: 100vw;
+      height: inherit;
+      background: url("../images/infoBan.jpg") 0 0 no-repeat #7f694a;
+      transform: translateX(-50%);
+
+      .container {
+        display: flex;
+        padding-top: 15px;
+        gap: 30px;
+        width: 1125px;
+        font-size: 42px;
+        line-height: 60px;
+        font-weight: 700;
+        color: white;
+
+        p {
+          padding-top: 70px;
+          flex: 1;
+        }
+        img {
+          flex-shrink: 0;
+          width: 275px;
+          height: 314px;
+          object-fit: contain;
+          object-position: center bottom;
+        }
+      }
+    }
+  }
+}

+ 21 - 0
src/views/Publications/Detail/index.vue

@@ -0,0 +1,21 @@
+<template>
+  <div class="publications-detail">
+    <div class="publications-detail__hd">
+      <div>
+        <div class="container">
+          <p>Cultural Relics Conservation and Security</p>
+
+          <img
+            src="https://en.capitalmuseum.org.cn/data/Publications/Magazines/35.jpg"
+          />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup></script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 116 - 0
src/views/Publications/Magazines/index.scss

@@ -0,0 +1,116 @@
+.magazines {
+  &-info {
+    display: flex;
+    gap: 30px;
+    margin: 70px 0 40px;
+
+    &-sidebar {
+      width: 252px;
+      border-right: 1px solid var(--van-primary-color);
+
+      li {
+        position: relative;
+        display: flex;
+        align-items: center;
+        margin-bottom: 48px;
+        gap: 10px;
+        font-size: 14px;
+        color: var(--gray-text-color);
+        line-height: 54px;
+        height: 54px;
+        font-weight: 700;
+        cursor: pointer;
+
+        &.active {
+          color: var(--van-primary-color);
+
+          &::after {
+            content: "";
+            position: absolute;
+            top: 50%;
+            right: -9px;
+            width: 9px;
+            height: 10px;
+            background: url("../images/ac.jpg") no-repeat center / contain;
+            transform: translateY(-50%);
+          }
+        }
+      }
+    }
+    &-main {
+      flex: 1;
+
+      :deep(p) {
+        margin-bottom: 22px;
+        line-height: 22px;
+        font-size: 14px;
+
+        i {
+          font-style: italic;
+        }
+      }
+    }
+  }
+
+  &-our {
+    margin-bottom: 25px;
+
+    &__title {
+      margin: 25px 0;
+      height: 64px;
+      text-align: center;
+      background: url("../images/bg_20.png") no-repeat center / contain;
+      line-height: 64px;
+      font-size: 24px;
+      font-weight: 700;
+    }
+    &__nav {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin-bottom: 20px;
+
+      li {
+        width: 140px;
+        height: 40px;
+        line-height: 40px;
+        text-align: center;
+        cursor: pointer;
+
+        &.active {
+          color: white;
+          background: #232323;
+        }
+      }
+    }
+    &__list {
+      display: flex;
+      flex-wrap: wrap;
+      margin: -8px;
+
+      li {
+        margin: 8px;
+        padding: 10px;
+        width: 283px;
+        height: 455px;
+        background: rgba(127, 105, 74, 0.6);
+        transition: background-color linear 0.3s;
+        cursor: pointer;
+
+        &:hover {
+          background: rgba(127, 105, 74);
+        }
+        .el-image {
+          margin-bottom: 15px;
+          width: 100%;
+          height: 358px;
+        }
+        p {
+          font-size: 16px;
+          color: white;
+          line-height: 24px;
+        }
+      }
+    }
+  }
+}

+ 76 - 0
src/views/Publications/Magazines/index.vue

@@ -0,0 +1,76 @@
+<template>
+  <div class="magazines">
+    <div class="magazines-info">
+      <ul class="magazines-info-sidebar">
+        <li
+          v-for="(item, idx) in MAGAZINES_INFO"
+          :key="idx"
+          :class="{ active: idx === curInfoIndex }"
+          tabindex="0"
+          aria-label="Link"
+          @click="curInfoIndex = idx"
+        >
+          <img :src="item.icon" />
+          <p>{{ item.name }}</p>
+        </li>
+      </ul>
+
+      <div
+        class="magazines-info-main"
+        v-html="MAGAZINES_INFO[curInfoIndex].conten"
+      />
+    </div>
+
+    <div
+      class="magazines-our"
+      data-aria-viewport-area
+      tabindex="0"
+      aria-description="You've reached the Our Magazines section, please use the tab key to go through the content."
+    >
+      <div class="magazines-our__title">Our Magazines</div>
+
+      <ul class="magazines-our__nav">
+        <li
+          v-for="(date, idx) in DATE"
+          :key="date"
+          :class="{ active: idx === curDateIndex }"
+          tabindex="0"
+          aria-label="Link"
+          @mouseenter="curDateIndex = idx"
+        >
+          {{ date }}
+        </li>
+      </ul>
+
+      <ul class="magazines-our__list">
+        <li
+          v-for="key in 10"
+          :key="key"
+          tabindex="0"
+          aria-label="Image link"
+          @click="
+            $router.push({ name: 'PublicationsDetail', query: { id: key } })
+          "
+        >
+          <ElImage
+            src="https://en.capitalmuseum.org.cn/data/Publications/Magazines/2021_3.jpg"
+          />
+          <p>Exhibition of Revolutionary Relics and Education</p>
+        </li>
+      </ul>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref } from "vue";
+import { MAGAZINES_INFO } from "../constants";
+
+const DATE = [2023, 2022, 2021, 2020, 2019, 2018, 2017];
+const curInfoIndex = ref(0);
+const curDateIndex = ref(0);
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

Diferenças do arquivo suprimidas por serem muito extensas
+ 36 - 0
src/views/Publications/constants.ts


BIN
src/views/Publications/images/1.png


BIN
src/views/Publications/images/2.png


BIN
src/views/Publications/images/ac.jpg


BIN
src/views/Publications/images/banner.jpg


BIN
src/views/Publications/images/bg_20.png


BIN
src/views/Publications/images/infoBan.jpg


BIN
src/views/Publications/images/p1.png


BIN
src/views/Publications/images/p2.png


BIN
src/views/Publications/images/p3.png


+ 51 - 0
src/views/Publications/index.scss

@@ -0,0 +1,51 @@
+.publications {
+  &-nav {
+    width: 100%;
+    padding-bottom: 8px;
+    background: url("@/assets/images/Visit/bg_3.png") left bottom repeat-x
+      #f1f1f1;
+    overflow: hidden;
+
+    ul {
+      margin: 0 auto;
+      display: flex;
+      width: 1180px;
+
+      li {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        width: 168px;
+        height: 108px;
+        cursor: pointer;
+
+        &.active {
+          background: url("@/assets/images/Visit/bg_1.jpg") center top no-repeat
+            #f1f1f1;
+        }
+        p {
+          margin-top: 5px;
+          font-size: 14px;
+          line-height: 18px;
+        }
+      }
+    }
+  }
+
+  .container {
+    width: 1180px;
+  }
+
+  &-banner {
+    margin-top: -60px;
+    display: block;
+    width: 100%;
+    height: 218px;
+    object-fit: cover;
+  }
+
+  .breadcrumb {
+    margin-top: 0;
+  }
+}

+ 70 - 0
src/views/Publications/index.vue

@@ -0,0 +1,70 @@
+<template>
+  <div class="publications">
+    <img
+      :aria-description="`You've reached the banner area of the ${curRoute?.name} page; this area has one image; please use the tab key to navigate through the content.`"
+      class="publications-banner"
+      src="./images/banner.jpg"
+    />
+
+    <div
+      class="publications-nav"
+      data-aria-viewport-area
+      aria-description="You've reached the secondary menu under the Exhibition section. This menu contains four options. To browse the content,  please use the tab key."
+    >
+      <ul>
+        <li
+          v-for="(item, index) in NAV_LIST"
+          :key="index"
+          :class="{
+            active: curRoute?.name === item.name,
+          }"
+          @click="$router.push(item.routeParams)"
+          @keydown.enter.passive="$router.push(item.routeParams)"
+          tabindex="0"
+          aria-label="Link"
+          :aria-description="item.name"
+        >
+          <img :src="item.img" alt="" />
+          <p>
+            {{ item.name }}
+          </p>
+        </li>
+      </ul>
+    </div>
+
+    <div class="container">
+      <Breadcrumb
+        :parents="[
+          {
+            label: 'Publications',
+            routeParams: {
+              name: 'Publications',
+            },
+          },
+        ]"
+        :cur-route="curRoute"
+      />
+
+      <PageTitle v-if="curRoute" :title="curRoute.name" />
+
+      <RouterView />
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { useRoute } from "vue-router";
+import { computed } from "vue";
+import PageTitle from "@/components/PageTitle/index.vue";
+import Breadcrumb from "@/components/Breadcrumb/index.vue";
+import { NAV_LIST } from "./constants";
+
+const route = useRoute();
+const curRoute = computed(() =>
+  NAV_LIST.find((item) => item.routeParams.name === route.name)
+);
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 6 - 4
src/views/Visit/index.vue

@@ -27,10 +27,12 @@
       </ul>
 
       <Breadcrumb
-        :parent="{
-          label: 'Visit',
-          pathName: 'Visit',
-        }"
+        :parents="[
+          {
+            label: 'Visit',
+            routeParams: { name: 'Visit' },
+          },
+        ]"
         :cur-route="curRoute"
       />