chenlei 1 rok pred
rodič
commit
a4309509ba

+ 1 - 0
.env.development

@@ -0,0 +1 @@
+VITE_BASE_URL=https://sit-shoubov2.4dage.com

+ 1 - 0
.env.production.development

@@ -0,0 +1 @@
+VITE_BASE_URL=https://sit-shoubov2.4dage.com

+ 4 - 0
components.d.ts

@@ -26,9 +26,13 @@ declare module 'vue' {
     RouterView: typeof import('vue-router')['RouterView']
     SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
     VanIcon: typeof import('vant/es')['Icon']
+    VanImage: typeof import('vant/es')['Image']
     VanSticky: typeof import('vant/es')['Sticky']
     VanSwipe: typeof import('vant/es')['Swipe']
     VanSwipeItem: typeof import('vant/es')['SwipeItem']
     VanSwitch: typeof import('vant/es')['Switch']
   }
+  export interface ComponentCustomProperties {
+    vLoading: typeof import('element-plus/es')['ElLoadingDirective']
+  }
 }

+ 2 - 0
package.json

@@ -11,6 +11,7 @@
     "type-check": "vue-tsc --build --force"
   },
   "dependencies": {
+    "@dage/service": "^1.0.3",
     "@dage/utils": "^1.0.2",
     "@vue/shared": "^3.4.27",
     "@vueuse/core": "^10.11.0",
@@ -31,6 +32,7 @@
     "install": "^0.13.0",
     "npm-run-all2": "^6.1.2",
     "sass": "^1.77.4",
+    "tslib": "^2.6.3",
     "typescript": "~5.4.0",
     "unplugin-auto-import": "^0.17.6",
     "unplugin-vue-components": "^0.27.0",

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 496 - 474
pnpm-lock.yaml


+ 27 - 1
src/App.vue

@@ -1,6 +1,10 @@
 <script setup lang="ts">
-import { onBeforeMount } from "vue";
+import { onBeforeMount, onMounted } from "vue";
 import { RouterView } from "vue-router";
+import { getBannerListApi } from "./api";
+import { useBaseStore } from "./stores/base";
+
+const baseStore = useBaseStore();
 
 onBeforeMount(() => {
   // 移动端和pc端的切换
@@ -19,6 +23,15 @@ onBeforeMount(() => {
     }
   }
 });
+
+onMounted(() => {
+  getBannerList();
+});
+
+const getBannerList = async () => {
+  const data = await getBannerListApi();
+  baseStore.bannerList = data;
+};
 </script>
 
 <template>
@@ -41,4 +54,17 @@ body.aria-active {
   max-width: 1200px;
   color: var(--black-text-color);
 }
+
+@font-face {
+  font-family: "SourceHanSans-Regular";
+  src: url("@/assets/fonts/SourceHanSansCN-Regular.otf");
+  font-weight: normal;
+  font-style: normal;
+}
+@font-face {
+  font-family: "SourceHanSans-Heavy";
+  src: url("@/assets/fonts/SourceHanSansCN-Heavy.otf");
+  font-weight: normal;
+  font-style: normal;
+}
 </style>

+ 15 - 0
src/api/index.ts

@@ -0,0 +1,15 @@
+import { requestByGet } from "@dage/service";
+
+export const getHomeListApi = () => {
+  return requestByGet("/api/show/index/getList");
+};
+
+export const getBannerListApi = () => {
+  return requestByGet("/api/show/poster/getList");
+};
+
+export const getBannerApi = (id: number) => {
+  return requestByGet(`/api/show/poster/detail/${id}`);
+};
+
+export * from "./learn";

+ 32 - 0
src/api/learn.ts

@@ -0,0 +1,32 @@
+import {
+  requestByGet,
+  requestByPost,
+  type PaginationParams,
+} from "@dage/service";
+
+export interface LearnPageListParams extends PaginationParams {
+  searchKey?: string;
+  type?: string;
+}
+
+export interface LearnPageItem {
+  id: number;
+  name: string;
+  address: string;
+  thumb: string;
+  remark: string;
+  dateStart: string;
+  dateEnd?: string;
+}
+
+export interface LearnDetail extends LearnPageItem {
+  rtf: string;
+}
+
+export const getLearnPageListApi = (params: LearnPageListParams) => {
+  return requestByPost("/api/show/learn/pageList", params);
+};
+
+export const getLearnDetailApi = (id: string | number) => {
+  return requestByGet<LearnDetail>(`/api/show/learn/detail/${id}`);
+};

+ 237 - 229
src/assets/css/base.css

@@ -1,229 +1,237 @@
-html,
-body,
-div,
-span,
-applet,
-object,
-iframe,
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
-p,
-blockquote,
-pre,
-a,
-abbr,
-acronym,
-address,
-big,
-cite,
-code,
-del,
-dfn,
-em,
-img,
-ins,
-kbd,
-q,
-s,
-samp,
-small,
-strike,
-strong,
-sub,
-sup,
-tt,
-var,
-b,
-u,
-i,
-center,
-dl,
-dt,
-dd,
-ol,
-ul,
-li,
-fieldset,
-form,
-label,
-legend,
-table,
-caption,
-tbody,
-tfoot,
-thead,
-tr,
-th,
-td,
-article,
-aside,
-canvas,
-details,
-embed,
-figure,
-figcaption,
-footer,
-header,
-hgroup,
-menu,
-nav,
-output,
-ruby,
-section,
-summary,
-time,
-mark,
-audio,
-video {
-    margin: 0;
-    padding: 0;
-    border: 0;
-    font-size: 100%;
-    font: inherit;
-    vertical-align: baseline;
-    box-sizing: border-box;
-}
-
-/* HTML5 display-role reset for older browsers */
-article,
-aside,
-details,
-figcaption,
-figure,
-footer,
-header,
-hgroup,
-menu,
-nav,
-section {
-    display: block;
-}
-
-body {
-    line-height: 1;
-    background-color: #f1f1f1;
-    font-family: Arial;
-}
-
-ol,
-ul {
-    list-style: none;
-}
-
-blockquote,
-q {
-    quotes: none;
-}
-
-blockquote:before,
-blockquote:after,
-q:before,
-q:after {
-    content: '';
-    content: none;
-}
-
-table {
-    border-collapse: collapse;
-    border-spacing: 0;
-}
-
-a {
-    color: #000;
-    text-decoration: none;
-}
-
-a:hover {
-    color: #000;
-    text-decoration: none;
-}
-
-/* element 输入框样式 */
-.el-input {
-    height: 30px;
-}
-
-.el-input__inner {
-    height: 30px;
-    border-radius: 15px;
-    line-height: 30px;
-}
-
-.el-input.is-active .el-input__inner,
-.el-input__inner:focus {
-    border-color: #ca000a;
-}
-
-/* 轮播图样式 */
-.el-carousel--horizontal {
-    height: 100%;
-}
-
-.el-carousel__container {
-    height: 100%;
-}
-
-.el-carousel__arrow {
-    display: none !important;
-}
-
-.el-carousel__button {
-    height: 4px;
-    background-color: #918784;
-    opacity: 1;
-}
-
-.el-carousel__indicator.is-active button {
-    background-color: #c7000b;
-}
-
-.el-carousel__indicators--horizontal {
-    bottom: 80px;
-}
-
-.el-select .el-input.is-focus .el-input__inner {
-    border-color: #c7000b;
-}
-
-.el-select .el-input__inner:focus {
-    border-color: #c7000b;
-}
-
-.el-select-dropdown__item.selected {
-    color: #c7000b;
-}
-
-.el-select-dropdown__item.hover,
-.el-select-dropdown__item:hover {
-    background-color: #c7000b;
-    color: #fff;
-}
-
-
-/* 置灰 */
-/* * {
-    -webkit-filter: grayscale(100%);
-    -moz-filter: grayscale(100%);
-    -ms-filter: grayscale(100%);
-    -o-filter: grayscale(100%);
-    filter: grayscale(100%);
-    filter: gray;
-} */
-
-.limit-line {
-    display: -webkit-box;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    -webkit-line-clamp: 1;
-    -webkit-box-orient: vertical;
-    word-break: break-all;
-    word-wrap: break-word;
-}
-
-.line-2 {
-    -webkit-line-clamp: 2;
-}
+html,
+body,
+div,
+span,
+applet,
+object,
+iframe,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+blockquote,
+pre,
+a,
+abbr,
+acronym,
+address,
+big,
+cite,
+code,
+del,
+dfn,
+em,
+img,
+ins,
+kbd,
+q,
+s,
+samp,
+small,
+strike,
+strong,
+sub,
+sup,
+tt,
+var,
+b,
+u,
+i,
+center,
+dl,
+dt,
+dd,
+ol,
+ul,
+li,
+fieldset,
+form,
+label,
+legend,
+table,
+caption,
+tbody,
+tfoot,
+thead,
+tr,
+th,
+td,
+article,
+aside,
+canvas,
+details,
+embed,
+figure,
+figcaption,
+footer,
+header,
+hgroup,
+menu,
+nav,
+output,
+ruby,
+section,
+summary,
+time,
+mark,
+audio,
+video {
+  margin: 0;
+  padding: 0;
+  border: 0;
+  font-size: 100%;
+  font: inherit;
+  vertical-align: baseline;
+  box-sizing: border-box;
+}
+
+/* HTML5 display-role reset for older browsers */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+  display: block;
+}
+
+body {
+  line-height: 1;
+  background-color: #f1f1f1;
+  font-family: Arial;
+}
+
+ol,
+ul {
+  list-style: none;
+}
+
+blockquote,
+q {
+  quotes: none;
+}
+
+blockquote:before,
+blockquote:after,
+q:before,
+q:after {
+  content: "";
+  content: none;
+}
+
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+a {
+  color: #000;
+  text-decoration: none;
+}
+
+a:hover {
+  color: #000;
+  text-decoration: none;
+}
+
+/* element 输入框样式 */
+.el-input {
+  height: 30px;
+}
+
+.el-input__inner {
+  height: 30px;
+  border-radius: 15px;
+  line-height: 30px;
+}
+
+.el-input.is-active .el-input__inner,
+.el-input__inner:focus {
+  border-color: #ca000a;
+}
+
+/* 轮播图样式 */
+.el-carousel--horizontal {
+  height: 100%;
+}
+
+.el-carousel__container {
+  height: 100%;
+}
+
+.el-carousel__arrow {
+  display: none !important;
+}
+
+.el-carousel__button {
+  height: 4px;
+  background-color: #918784;
+  opacity: 1;
+}
+
+.el-carousel__indicator.is-active button {
+  background-color: #c7000b;
+}
+
+.el-carousel__indicators--horizontal {
+  bottom: 80px;
+}
+
+.el-select .el-input.is-focus .el-input__inner {
+  border-color: #c7000b;
+}
+
+.el-select .el-input__inner:focus {
+  border-color: #c7000b;
+}
+
+.el-select-dropdown__item.selected {
+  color: #c7000b;
+}
+
+.el-select-dropdown__item.hover,
+.el-select-dropdown__item:hover {
+  background-color: #c7000b;
+  color: #fff;
+}
+
+/* 置灰 */
+/* * {
+    -webkit-filter: grayscale(100%);
+    -moz-filter: grayscale(100%);
+    -ms-filter: grayscale(100%);
+    -o-filter: grayscale(100%);
+    filter: grayscale(100%);
+    filter: gray;
+} */
+
+.limit-line {
+  display: -webkit-box;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  -webkit-line-clamp: 1;
+  -webkit-box-orient: vertical;
+  word-break: break-all;
+  word-wrap: break-word;
+}
+
+.line-2 {
+  -webkit-line-clamp: 2;
+}
+
+.no-more {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  font-size: 18px;
+  color: var(--gray-text-color);
+  transform: translate(-50%, -50%);
+}

BIN
src/assets/fonts/SourceHanSansCN-Heavy.otf


BIN
src/assets/fonts/SourceHanSansCN-Regular.otf


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 569 - 568
src/components/Layout/index.vue


+ 6 - 0
src/configure.ts

@@ -0,0 +1,6 @@
+import { initial } from "@dage/service";
+
+initial({
+  fetch: window.fetch.bind(window),
+  baseURL: import.meta.env.VITE_BASE_URL,
+});

+ 1 - 0
src/main.ts

@@ -1,4 +1,5 @@
 import "./assets/css/base.css";
+import "./configure";
 
 import { createApp } from "vue";
 import { createPinia } from "pinia";

+ 8 - 0
src/stores/base.ts

@@ -0,0 +1,8 @@
+import { ref } from "vue";
+import { defineStore } from "pinia";
+
+export const useBaseStore = defineStore("base", () => {
+  const bannerList = ref<{ id: number; name: string; thumbPc: string }[]>([]);
+
+  return { bannerList };
+});

+ 0 - 12
src/stores/counter.ts

@@ -1,12 +0,0 @@
-import { ref, computed } from 'vue'
-import { defineStore } from 'pinia'
-
-export const useCounterStore = defineStore('counter', () => {
-  const count = ref(0)
-  const doubleCount = computed(() => count.value * 2)
-  function increment() {
-    count.value++
-  }
-
-  return { count, doubleCount, increment }
-})

+ 430 - 404
src/views/Home/index.vue

@@ -1,404 +1,430 @@
-<template>
-  <div class="home">
-    <van-swipe
-      ref="swipeRef"
-      indicator-color="white"
-      :height="swipeHeight"
-      :autoplay="3000"
-      lazy-render
-    >
-      <van-swipe-item>1</van-swipe-item>
-      <van-swipe-item>2</van-swipe-item>
-      <van-swipe-item>3</van-swipe-item>
-      <van-swipe-item>4</van-swipe-item>
-
-      <template #indicator="{ active, total }">
-        <ul class="home-indicator">
-          <li
-            v-for="val in total"
-            :key="val"
-            :class="['home-indicator__item', { active: active + 1 === val }]"
-            @mouseover="swipeRef?.swipeTo(val - 1)"
-          />
-        </ul>
-      </template>
-    </van-swipe>
-
-    <div
-      class="bottomNav"
-      data-aria-viewport-area
-      tabindex="0"
-      aria-description="You've reached the pop-up window section; this section contains four URLs; please use the tab key to go through the content."
-    >
-      <div
-        class="t1"
-        @click="$router.push('/Layout/Visit/2')"
-        tabindex="0"
-        aria-label="Link"
-        aria-description="Reservation"
-      >
-        <div class="title">
-          <span class="aria-theme-independent"> Reservation </span>
-        </div>
-        <div class="info">
-          <router-link
-            class="p"
-            to="/Layout/Visit/2"
-            replace
-            tabindex="0"
-            aria-label="Image link"
-            aria-description="Reservation"
-          >
-            <img src="../../assets/images/bott1.jpg" alt="Reservation" />
-          </router-link>
-          <div class="d">
-            <p class="n1 aria-theme-independent" tabindex="0">
-              Online Reservation
-            </p>
-            <p class="n2 aria-theme-independent" tabindex="0">
-              Telephone Reservation
-            </p>
-            <p class="n3" tabindex="0">
-              Individual <span>+86 (10) 63393339</span>
-            </p>
-            <p class="n3" tabindex="0">Group <span>+86 (10) 63370458</span></p>
-          </div>
-        </div>
-      </div>
-      <div
-        class="t2"
-        @click="$router.push('/Layout/Visit/1')"
-        tabindex="0"
-        aria-label="Link"
-        aria-description="Visit Info"
-      >
-        <div class="title">
-          <span class="aria-theme-independent">Visit Info</span>
-        </div>
-        <div class="info">
-          <router-link
-            class="p"
-            to="/Layout/Visit/1"
-            replace
-            tabindex="0"
-            aria-label="Image link"
-            aria-description="Hours, Direction & Admission"
-          >
-            <img
-              src="../../assets/images/bott2.jpg"
-              alt="Hours, Direction & Admission"
-            />
-          </router-link>
-          <div class="d">
-            <p class="n4 aria-theme-independent" tabindex="0">
-              Opening Hours 09:00-17:00
-            </p>
-            <p class="n5" tabindex="0">No admission after 16:00</p>
-            <p class="n5" tabindex="0">Closed on Monday</p>
-            <p class="n6 aria-theme-independent" tabindex="0">Phone</p>
-            <p tabindex="0">+86 (10) 63370491</p>
-          </div>
-        </div>
-      </div>
-      <div
-        class="t3"
-        @click="botskip()"
-        tabindex="0"
-        aria-label="Link"
-        aria-description="Partners & Connections"
-      >
-        <div class="title">
-          <span class="aria-theme-independent">Partners & Connections</span>
-        </div>
-        <div class="info">
-          <router-link
-            class="p"
-            to="/Layout/About"
-            replace
-            tabindex="0"
-            aria-label="Image link"
-            aria-description="Partners & Connections"
-          >
-            <img
-              src="../../assets/images/bott3.jpg"
-              alt="Partners & Connections"
-            />
-          </router-link>
-          <div class="d">
-            <p class="n7 aria-theme-independent" tabindex="0">
-              Partners & Connections
-            </p>
-          </div>
-        </div>
-      </div>
-      <div
-        class="t4"
-        @click="$router.push('/Layout/Events')"
-        tabindex="0"
-        aria-label="Link"
-        aria-description="Events"
-      >
-        <div class="title">
-          <span class="aria-theme-independent">Events</span>
-        </div>
-        <div class="info">
-          <router-link
-            class="p"
-            to="/Layout/Events"
-            replace
-            tabindex="0"
-            aria-label="Image link"
-            aria-description="Events"
-          >
-            <img src="../../assets/images/bott4.jpg" alt="Events" />
-          </router-link>
-          <div class="d">
-            <p class="n8 aria-theme-independent" tabindex="0">Events</p>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-import type { SwipeInstance } from "vant";
-import { ref } from "vue";
-import { useRouter } from "vue-router";
-
-const router = useRouter();
-const swipeHeight = window.innerHeight - 105;
-const swipeRef = ref<SwipeInstance>();
-
-const botskip = () => {
-  router.push("/Layout/About").catch(() => {});
-  setTimeout(() => {
-    window.scrollTo({ top: 1307, behavior: "smooth" });
-  }, 100);
-};
-</script>
-
-<style lang="scss" scoped>
-.home {
-  position: relative;
-
-  img {
-    width: 100%;
-    height: 100%;
-    object-fit: cover;
-    cursor: pointer;
-  }
-}
-
-.home-indicator {
-  position: absolute;
-  left: 50%;
-  bottom: 80px;
-  transform: translateX(-50%);
-
-  &__item {
-    display: inline-block;
-    padding: 12px 4px;
-    cursor: pointer;
-
-    &.active::after {
-      background: var(--van-primary-color);
-    }
-    &::after {
-      content: "";
-      display: block;
-      width: 30px;
-      height: 4px;
-      background: #918784;
-    }
-  }
-}
-
-.bottomNav {
-  padding: 0 100px;
-  background-color: var(--topnav-bg-color);
-  width: 100%;
-  height: 55px;
-  position: absolute;
-  left: 0;
-  bottom: 0;
-  z-index: 4;
-  display: flex;
-  > div {
-    display: block;
-    cursor: pointer;
-    width: 25%;
-    height: 58px;
-    position: relative;
-    transform: translateY(-3px);
-    .title {
-      font-size: 14px;
-      color: var(--van-white);
-      line-height: 55px;
-      text-align: center;
-      width: 100%;
-      span {
-        padding-left: 30px;
-      }
-    }
-    .info {
-      transition: all 0.3s;
-      height: 0;
-      overflow: hidden;
-      width: 100%;
-      position: absolute;
-      left: 0;
-      bottom: 0;
-      z-index: 99;
-      .d {
-        font-size: 14px;
-        color: #ccc;
-        background: #000;
-        padding: 20px;
-      }
-      .p {
-        display: block;
-        img {
-          vertical-align: bottom;
-        }
-      }
-    }
-  }
-  .t1:hover .info {
-    height: 320px;
-  }
-  .t2:hover .info {
-    height: 320px;
-  }
-  .t3:hover .info {
-    height: 270px;
-  }
-  .t4:hover .info {
-    height: 270px;
-  }
-  .t1:focus-within .info {
-    height: 320px;
-  }
-  .t2:focus-within .info {
-    height: 320px;
-  }
-  .t3:focus-within .info {
-    height: 270px;
-  }
-  .t4:focus-within .info {
-    height: 270px;
-  }
-  .t1 {
-    .title {
-      span {
-        background: url("../../assets/images/bottom_ico1.png") no-repeat left
-          center;
-      }
-    }
-    .p {
-      border-bottom: 3px solid #1116e5;
-    }
-    border-top: 3px solid #1116e5;
-  }
-  .t2 {
-    .title {
-      span {
-        background: url("../../assets/images/bottom_ico2.png") no-repeat left
-          center;
-      }
-    }
-    .p {
-      border-bottom: 3px solid #a211e5;
-    }
-    border-top: 3px solid #a211e5;
-  }
-  .t3 {
-    .title {
-      span {
-        background: url("../../assets/images/bottom_ico3.png") no-repeat left
-          center;
-      }
-    }
-    .p {
-      border-bottom: 3px solid #229382;
-    }
-    .d {
-      padding-top: 40px !important;
-      padding-bottom: 40px !important;
-    }
-    border-top: 3px solid #229382;
-  }
-  .t4 {
-    .title {
-      span {
-        background: url("../../assets/images/bottom_ico4.png") no-repeat left
-          center;
-      }
-    }
-    .p {
-      border-bottom: 3px solid #7a9322;
-    }
-    .d {
-      padding-top: 40px !important;
-      padding-bottom: 40px !important;
-    }
-
-    border-top: 3px solid #7a9322;
-  }
-  p {
-    margin-bottom: 5px;
-  }
-  .n1 {
-    line-height: 30px;
-    background: url(../../assets/images/bottom_sub_ico1.png) no-repeat left
-      center;
-    text-indent: 25px;
-    & > a {
-      color: #ff0000;
-      text-decoration: underline;
-      margin-left: 20px;
-    }
-  }
-  .n2 {
-    line-height: 30px;
-    background: url(../../assets/images/bottom_sub_ico2.png) no-repeat left
-      center;
-    text-indent: 25px;
-    margin-bottom: 10px;
-  }
-  .n3 {
-    width: 200px;
-    margin-bottom: 5px;
-    & > span {
-      float: right;
-    }
-  }
-  .n4 {
-    line-height: 30px;
-    background: url(../../assets/images/bottom_sub_ico3.png) no-repeat left
-      center;
-    text-indent: 25px;
-  }
-  .n5 {
-    font-size: 12px;
-    color: #828282;
-    margin-bottom: 0;
-  }
-  .n6 {
-    line-height: 30px;
-    background: url(../../assets/images/bottom_sub_ico2.png) no-repeat left
-      center;
-    text-indent: 25px;
-  }
-  .n7 {
-    font-size: 18px;
-    background: url(../../assets/images/bottom_ico3.png) no-repeat left center;
-    padding-left: 30px;
-  }
-  .n8 {
-    font-size: 18px;
-    background: url(../../assets/images/bottom_ico4.png) no-repeat left center;
-    padding-left: 30px;
-  }
-}
-</style>
+<template>
+  <div class="home">
+    <van-swipe
+      v-loading="loading"
+      ref="swipeRef"
+      indicator-color="white"
+      :height="swipeHeight"
+      :autoplay="3000"
+      lazy-render
+    >
+      <van-swipe-item v-for="item in list" :key="item.id">
+        <van-image
+          width="100%"
+          height="100%"
+          :src="baseUrl + item.thumbPc"
+          fit="cover"
+          style="cursor: pointer"
+          @click="item.link && $router.push(item.link)"
+        />
+      </van-swipe-item>
+
+      <template #indicator="{ active, total }">
+        <ul class="home-indicator">
+          <li
+            v-for="val in total"
+            :key="val"
+            :class="['home-indicator__item', { active: active + 1 === val }]"
+            @mouseover="swipeRef?.swipeTo(val - 1)"
+          />
+        </ul>
+      </template>
+    </van-swipe>
+
+    <div
+      class="bottomNav"
+      data-aria-viewport-area
+      tabindex="0"
+      aria-description="You've reached the pop-up window section; this section contains four URLs; please use the tab key to go through the content."
+    >
+      <div
+        class="t1"
+        @click="$router.push('/Layout/Visit/2')"
+        tabindex="0"
+        aria-label="Link"
+        aria-description="Reservation"
+      >
+        <div class="title">
+          <span class="aria-theme-independent"> Reservation </span>
+        </div>
+        <div class="info">
+          <router-link
+            class="p"
+            to="/Layout/Visit/2"
+            replace
+            tabindex="0"
+            aria-label="Image link"
+            aria-description="Reservation"
+          >
+            <img src="../../assets/images/bott1.jpg" alt="Reservation" />
+          </router-link>
+          <div class="d">
+            <p class="n1 aria-theme-independent" tabindex="0">
+              Online Reservation
+            </p>
+            <p class="n2 aria-theme-independent" tabindex="0">
+              Telephone Reservation
+            </p>
+            <p class="n3" tabindex="0">
+              Individual <span>+86 (10) 63393339</span>
+            </p>
+            <p class="n3" tabindex="0">Group <span>+86 (10) 63370458</span></p>
+          </div>
+        </div>
+      </div>
+      <div
+        class="t2"
+        @click="$router.push('/Layout/Visit/1')"
+        tabindex="0"
+        aria-label="Link"
+        aria-description="Visit Info"
+      >
+        <div class="title">
+          <span class="aria-theme-independent">Visit Info</span>
+        </div>
+        <div class="info">
+          <router-link
+            class="p"
+            to="/Layout/Visit/1"
+            replace
+            tabindex="0"
+            aria-label="Image link"
+            aria-description="Hours, Direction & Admission"
+          >
+            <img
+              src="../../assets/images/bott2.jpg"
+              alt="Hours, Direction & Admission"
+            />
+          </router-link>
+          <div class="d">
+            <p class="n4 aria-theme-independent" tabindex="0">
+              Opening Hours 09:00-17:00
+            </p>
+            <p class="n5" tabindex="0">No admission after 16:00</p>
+            <p class="n5" tabindex="0">Closed on Monday</p>
+            <p class="n6 aria-theme-independent" tabindex="0">Phone</p>
+            <p tabindex="0">+86 (10) 63370491</p>
+          </div>
+        </div>
+      </div>
+      <div
+        class="t3"
+        @click="botskip()"
+        tabindex="0"
+        aria-label="Link"
+        aria-description="Partners & Connections"
+      >
+        <div class="title">
+          <span class="aria-theme-independent">Partners & Connections</span>
+        </div>
+        <div class="info">
+          <router-link
+            class="p"
+            to="/Layout/About"
+            replace
+            tabindex="0"
+            aria-label="Image link"
+            aria-description="Partners & Connections"
+          >
+            <img
+              src="../../assets/images/bott3.jpg"
+              alt="Partners & Connections"
+            />
+          </router-link>
+          <div class="d">
+            <p class="n7 aria-theme-independent" tabindex="0">
+              Partners & Connections
+            </p>
+          </div>
+        </div>
+      </div>
+      <div
+        class="t4"
+        @click="$router.push('/Layout/Events')"
+        tabindex="0"
+        aria-label="Link"
+        aria-description="Events"
+      >
+        <div class="title">
+          <span class="aria-theme-independent">Events</span>
+        </div>
+        <div class="info">
+          <router-link
+            class="p"
+            to="/Layout/Events"
+            replace
+            tabindex="0"
+            aria-label="Image link"
+            aria-description="Events"
+          >
+            <img src="../../assets/images/bott4.jpg" alt="Events" />
+          </router-link>
+          <div class="d">
+            <p class="n8 aria-theme-independent" tabindex="0">Events</p>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { getHomeListApi } from "@/api";
+import { getBaseURL } from "@dage/service";
+import type { SwipeInstance } from "vant";
+import { onMounted, ref } from "vue";
+import { useRouter } from "vue-router";
+
+const router = useRouter();
+const swipeHeight = window.innerHeight - 105;
+const swipeRef = ref<SwipeInstance>();
+const loading = ref(false);
+const list = ref<any[]>([]);
+const baseUrl = getBaseURL();
+
+onMounted(() => {
+  getList();
+});
+
+const getList = async () => {
+  try {
+    loading.value = true;
+    const data = await getHomeListApi();
+    list.value = data;
+  } finally {
+    loading.value = false;
+  }
+};
+
+const botskip = () => {
+  router.push("/Layout/About").catch(() => {});
+  setTimeout(() => {
+    window.scrollTo({ top: 1307, behavior: "smooth" });
+  }, 100);
+};
+</script>
+
+<style lang="scss" scoped>
+.home {
+  position: relative;
+
+  img {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+    cursor: pointer;
+  }
+}
+
+.home-indicator {
+  position: absolute;
+  left: 50%;
+  bottom: 80px;
+  transform: translateX(-50%);
+
+  &__item {
+    display: inline-block;
+    padding: 12px 4px;
+    cursor: pointer;
+
+    &.active::after {
+      background: var(--van-primary-color);
+    }
+    &::after {
+      content: "";
+      display: block;
+      width: 30px;
+      height: 4px;
+      background: #918784;
+    }
+  }
+}
+
+.bottomNav {
+  padding: 0 100px;
+  background-color: var(--topnav-bg-color);
+  width: 100%;
+  height: 55px;
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  z-index: 4;
+  display: flex;
+  > div {
+    display: block;
+    cursor: pointer;
+    width: 25%;
+    height: 58px;
+    position: relative;
+    transform: translateY(-3px);
+    .title {
+      font-size: 14px;
+      color: var(--van-white);
+      line-height: 55px;
+      text-align: center;
+      width: 100%;
+      span {
+        padding: 5px 0 5px 30px;
+      }
+    }
+    .info {
+      transition: all 0.3s;
+      height: 0;
+      overflow: hidden;
+      width: 100%;
+      position: absolute;
+      left: 0;
+      bottom: 0;
+      z-index: 99;
+      .d {
+        font-size: 14px;
+        color: #ccc;
+        background: #000;
+        padding: 20px;
+      }
+      .p {
+        display: block;
+        img {
+          vertical-align: bottom;
+        }
+      }
+    }
+  }
+  .t1:hover .info {
+    height: 320px;
+  }
+  .t2:hover .info {
+    height: 320px;
+  }
+  .t3:hover .info {
+    height: 270px;
+  }
+  .t4:hover .info {
+    height: 270px;
+  }
+  .t1:focus-within .info {
+    height: 320px;
+  }
+  .t2:focus-within .info {
+    height: 320px;
+  }
+  .t3:focus-within .info {
+    height: 270px;
+  }
+  .t4:focus-within .info {
+    height: 270px;
+  }
+  .t1 {
+    .title {
+      span {
+        background: url("../../assets/images/bottom_ico1.png") no-repeat left
+          center;
+      }
+    }
+    .p {
+      border-bottom: 3px solid #1116e5;
+    }
+    border-top: 3px solid #1116e5;
+  }
+  .t2 {
+    .title {
+      span {
+        background: url("../../assets/images/bottom_ico2.png") no-repeat left
+          center;
+      }
+    }
+    .p {
+      border-bottom: 3px solid #a211e5;
+    }
+    border-top: 3px solid #a211e5;
+  }
+  .t3 {
+    .title {
+      span {
+        background: url("../../assets/images/bottom_ico3.png") no-repeat left
+          center;
+      }
+    }
+    .p {
+      border-bottom: 3px solid #229382;
+    }
+    .d {
+      padding-top: 40px !important;
+      padding-bottom: 40px !important;
+    }
+    border-top: 3px solid #229382;
+  }
+  .t4 {
+    .title {
+      span {
+        background: url("../../assets/images/bottom_ico4.png") no-repeat left
+          center;
+      }
+    }
+    .p {
+      border-bottom: 3px solid #7a9322;
+    }
+    .d {
+      padding-top: 40px !important;
+      padding-bottom: 40px !important;
+    }
+
+    border-top: 3px solid #7a9322;
+  }
+  p {
+    margin-bottom: 5px;
+  }
+  .n1 {
+    line-height: 30px;
+    background: url(../../assets/images/bottom_sub_ico1.png) no-repeat left
+      center;
+    text-indent: 25px;
+    & > a {
+      color: #ff0000;
+      text-decoration: underline;
+      margin-left: 20px;
+    }
+  }
+  .n2 {
+    line-height: 30px;
+    background: url(../../assets/images/bottom_sub_ico2.png) no-repeat left
+      center;
+    text-indent: 25px;
+    margin-bottom: 10px;
+  }
+  .n3 {
+    width: 200px;
+    margin-bottom: 5px;
+    & > span {
+      float: right;
+    }
+  }
+  .n4 {
+    line-height: 30px;
+    background: url(../../assets/images/bottom_sub_ico3.png) no-repeat left
+      center;
+    text-indent: 25px;
+  }
+  .n5 {
+    font-size: 12px;
+    color: #828282;
+    margin-bottom: 0;
+  }
+  .n6 {
+    line-height: 30px;
+    background: url(../../assets/images/bottom_sub_ico2.png) no-repeat left
+      center;
+    text-indent: 25px;
+  }
+  .n7 {
+    font-size: 18px;
+    background: url(../../assets/images/bottom_ico3.png) no-repeat left center;
+    padding-left: 30px;
+  }
+  .n8 {
+    font-size: 18px;
+    background: url(../../assets/images/bottom_ico4.png) no-repeat left center;
+    padding-left: 30px;
+  }
+}
+</style>

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

@@ -1,57 +1,63 @@
-.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;
-    }
-  }
-}
+.learn-detail {
+  display: flex;
+  gap: 30px;
+  padding: 20px 50px 10px;
+  border-top: 1px solid var(--black-text-color);
+
+  &-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,
+    &__address::before,
+    &__team::before {
+      content: "";
+      position: absolute;
+      top: 50%;
+      left: 0;
+      width: 25px;
+      height: 25px;
+      transform: translateY(-50%);
+    }
+    &__date::before {
+      background: url("@/assets/images/date.png") no-repeat center / contain;
+    }
+    &__address::before {
+      background: url("@/assets/images/address.png") no-repeat center / contain;
+    }
+    &__team::before {
+      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;
+    }
+    &__rtf {
+      :deep(p) {
+        margin-bottom: 10px;
+        line-height: 20px;
+        color: #1f1d1d;
+        font-size: 14px;
+      }
+      :deep(.media-wrap) {
+        text-align: center;
+      }
+    }
+  }
+}

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

@@ -1,39 +1,102 @@
-<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>
+<template>
+  <div
+    v-loading="loading"
+    class="learn-detail"
+    tabindex="0"
+    data-aria-viewport-area
+    :aria-description="detail?.name"
+  >
+    <div class="learn-detail-sidebar">
+      <ElImage
+        v-if="detail"
+        fit="cover"
+        tabindex="0"
+        aria-description="title"
+        alt=""
+        :src="baseUrl + detail.thumb"
+      />
+
+      <p class="learn-detail-sidebar__date" tabindex="0">
+        {{ date }}
+      </p>
+      <p class="learn-detail-sidebar__address" tabindex="0">
+        {{ detail?.address }}
+      </p>
+      <p class="learn-detail-sidebar__team" tabindex="0">
+        {{ detail?.remark }}
+      </p>
+    </div>
+
+    <div class="learn-detail-inner">
+      <h3 class="learn-detail-inner__title" tabindex="0">
+        {{ detail?.name }}
+      </h3>
+
+      <div
+        v-for="item in rtf"
+        :key="item.id"
+        v-html="item.txt"
+        class="learn-detail-inner__rtf"
+      />
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { getLearnDetailApi, type LearnDetail } from "@/api";
+import { getBaseURL } from "@dage/service";
+import { computed, onMounted, ref } from "vue";
+import { useRoute } from "vue-router";
+
+const route = useRoute();
+const loading = ref(false);
+const detail = ref<null | LearnDetail>(null);
+const rtf = ref<{ id: number; txt: string }[]>([]);
+const baseUrl = getBaseURL();
+const date = computed(() => {
+  if (!detail.value) return;
+
+  let endDate: Date | null = null;
+  const date = new Date(detail.value.dateStart.replace(" ", "T"));
+
+  if (detail.value.dateEnd) {
+    endDate = new Date(detail.value.dateEnd.replace(" ", "T"));
+  }
+
+  const options: Intl.DateTimeFormatOptions = {
+    year: "numeric",
+    month: "long",
+    day: "numeric",
+    hour: "numeric",
+    minute: "numeric",
+    hour12: true,
+  };
+
+  return endDate
+    ? `${date.toLocaleString("en-US", options)} - ${endDate.toLocaleString(
+        "en-US",
+        options
+      )}`
+    : date.toLocaleString("en-US", options);
+});
+
+onMounted(() => {
+  getDetail();
+});
+
+const getDetail = async () => {
+  try {
+    loading.value = true;
+
+    const data = await getLearnDetailApi(route.query.id as string);
+    detail.value = data;
+    rtf.value = JSON.parse(data.rtf).txtArr;
+  } finally {
+    loading.value = false;
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

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

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

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

@@ -1,18 +1,59 @@
-<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>
+<template>
+  <div v-loading="loading" class="learn-engage-container">
+    <Item v-for="item in list" :key="item.id" :item="item" />
+
+    <p v-if="!total && !loading" class="no-more">no more</p>
+  </div>
+
+  <div
+    v-if="total"
+    style="display: flex; justify-content: center; padding: 30px 0"
+  >
+    <Pagination :total="total" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, watch } from "vue";
+import { getLearnPageListApi, type LearnPageItem } from "@/api";
+import Pagination from "@/components/Pagination/index.vue";
+import Item from "../components/Item.vue";
+
+const props = defineProps<{
+  curType?: string;
+}>();
+const pageNum = ref(1);
+const total = ref(0);
+const loading = ref(false);
+const list = ref<LearnPageItem[]>([]);
+
+const getList = async () => {
+  try {
+    loading.value = true;
+    const data = await getLearnPageListApi({
+      pageNum: pageNum.value,
+      pageSize: 9,
+      type: props.curType,
+    });
+
+    total.value = data.total;
+    list.value = data.records;
+  } finally {
+    loading.value = false;
+  }
+};
+
+watch(
+  () => props.curType,
+  () => {
+    getList();
+  },
+  {
+    immediate: true,
+  }
+);
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

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

@@ -1,78 +1,110 @@
-<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>
+<template>
+  <div
+    class="learn-item"
+    tabindex="0"
+    aria-label="Link"
+    @click="
+      $router.push({
+        name: 'LearnEngageDetail',
+        query: { id: item.id, type: $route.params.type },
+      })
+    "
+  >
+    <ElImage fit="cover" class="learn-item__img" :src="baseUrl + item.thumb" />
+
+    <div class="learn-item__inner">
+      <p class="learn-item__title limit-line" tabindex="0">
+        {{ item.name }}
+      </p>
+
+      <p class="learn-item__content limit-line" tabindex="0">
+        Venue:{{ item.address }}
+      </p>
+      <p class="learn-item__content limit-line" tabindex="0">
+        Date:{{ date }}
+      </p>
+      <p class="learn-item__content limit-line" tabindex="0">
+        Limit:{{ item.remark }}
+      </p>
+    </div>
+
+    <div class="learn-item__more" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import type { LearnPageItem } from "@/api";
+import { getBaseURL } from "@dage/service";
+import { computed } from "vue";
+
+const props = defineProps<{
+  item: LearnPageItem;
+}>();
+
+const baseUrl = getBaseURL();
+const date = computed(() => {
+  let endDate: Date | null = null;
+  const date = new Date(props.item.dateStart.replace(" ", "T"));
+
+  if (props.item.dateEnd) {
+    endDate = new Date(props.item.dateEnd.replace(" ", "T"));
+  }
+
+  const options: Intl.DateTimeFormatOptions = {
+    year: "numeric",
+    month: "long",
+    day: "numeric",
+    hour: "numeric",
+    minute: "numeric",
+    hour12: true,
+  };
+
+  return endDate
+    ? `${date.toLocaleString("en-US", options)} - ${endDate.toLocaleString(
+        "en-US",
+        options
+      )}`
+    : date.toLocaleString("en-US", options);
+});
+</script>
+
+<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>

+ 131 - 115
src/views/LearnEngage/index.vue

@@ -1,115 +1,131 @@
-<template>
-  <div class="learn-engage">
-    <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 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>
-
-<script lang="ts" setup>
-import { useRoute } from "vue-router";
-import { computed } from "vue";
-import Breadcrumb from "@/components/Breadcrumb/index.vue";
-import StudentsIcon from "./images/1.png";
-import AdultsIcon from "./images/2.png";
-import FamiliesIcon from "./images/3.png";
-
-const NAV_LIST = [
-  {
-    name: "For Students",
-    img: StudentsIcon,
-    routeParams: {
-      name: "LearnEngageList",
-      params: {
-        type: "Students",
-      },
-    },
-  },
-  {
-    name: "For Adults",
-    img: AdultsIcon,
-    routeParams: {
-      name: "LearnEngageList",
-      params: {
-        type: "Adults",
-      },
-    },
-  },
-  {
-    name: "For Families & Children",
-    img: FamiliesIcon,
-    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.routeParams.params.type === route.params.type)
-);
-const showNav = computed(() => route.name === "LearnEngageList");
-</script>
-
-<style lang="scss" scoped>
-@import "./index.scss";
-</style>
+<template>
+  <div class="learn-engage">
+    <ElImage
+      class="learn-engage-banner"
+      tabindex="0"
+      fit="cover"
+      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="bannerUrl"
+    />
+
+    <div class="container">
+      <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
+        :cur-type="
+          NAV_LIST.find((i) => $route.params.type === i.routeParams.params.type)
+            ?.name
+        "
+      />
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { useRoute } from "vue-router";
+import { computed } from "vue";
+import { getBaseURL } from "@dage/service";
+import { useBaseStore } from "@/stores/base";
+import Breadcrumb from "@/components/Breadcrumb/index.vue";
+import StudentsIcon from "./images/1.png";
+import AdultsIcon from "./images/2.png";
+import FamiliesIcon from "./images/3.png";
+
+const NAV_LIST = [
+  {
+    name: "For Students",
+    img: StudentsIcon,
+    routeParams: {
+      name: "LearnEngageList",
+      params: {
+        type: "Students",
+      },
+    },
+  },
+  {
+    name: "For Adults",
+    img: AdultsIcon,
+    routeParams: {
+      name: "LearnEngageList",
+      params: {
+        type: "Adults",
+      },
+    },
+  },
+  {
+    name: "For Families & Children",
+    img: FamiliesIcon,
+    routeParams: {
+      name: "LearnEngageList",
+      params: {
+        type: "Families",
+      },
+    },
+  },
+];
+
+const baseUrl = getBaseURL();
+const baseStore = useBaseStore();
+const route = useRoute();
+const bannerUrl = computed(() => {
+  return (
+    baseUrl +
+    baseStore.bannerList.find((i) => i.name === "Learn&Engage")?.thumbPc
+  );
+});
+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.routeParams.params.type === route.params.type)
+);
+const showNav = computed(() => route.name === "LearnEngageList");
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 94 - 0
src/views/Visit/AppointmentGuide/components/Step.vue

@@ -0,0 +1,94 @@
+<template>
+  <div class="app-step">
+    <div
+      class="app-step__item active"
+      tabindex="0"
+      aria-description="Register a WeChat account"
+      @click="scrollTo(0)"
+    >
+      <div class="app-step__item__circle">1</div>
+      <p>Register a WeChat account</p>
+    </div>
+    <div
+      class="app-step__item"
+      tabindex="0"
+      aria-description="Follow the official account"
+      @click="scrollTo(1)"
+    >
+      <div class="app-step__item__circle">2</div>
+      <p>Follow the official account</p>
+    </div>
+    <div
+      class="app-step__item"
+      tabindex="0"
+      aria-description="Apply for an appointment"
+      @click="scrollTo(2)"
+    >
+      <div class="app-step__item__circle">3</div>
+      <p>Apply for an appointment</p>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+const ARR = [685, 1735, 2275];
+
+const scrollTo = (i: number) => {
+  window.scrollTo({ top: ARR[i], behavior: "smooth" });
+};
+</script>
+
+<style lang="scss" scoped>
+.app-step {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 212px;
+
+  &__item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    position: relative;
+    cursor: pointer;
+
+    &:not(:last-child)::after {
+      content: "";
+      position: absolute;
+      top: 23px;
+      right: -222px;
+      width: 232px;
+      height: 1px;
+      border-top: 3px solid #d7cbb2;
+    }
+    &.active {
+      .app-step__item__circle {
+        font-weight: bold;
+        background-color: var(--van-primary-color);
+      }
+      p {
+        color: var(--van-primary-color);
+      }
+    }
+    &__circle {
+      width: 55px;
+      height: 55px;
+      line-height: 55px;
+      text-align: center;
+      font-size: 24px;
+      color: white;
+      border-radius: 50%;
+      background: #d7cbb2;
+    }
+    p {
+      margin-top: 10px;
+      width: 128px;
+      text-align: center;
+      color: #d7cbb2;
+      font-size: 15px;
+      line-height: 19px;
+    }
+  }
+}
+</style>

+ 43 - 0
src/views/Visit/AppointmentGuide/index.scss

@@ -0,0 +1,43 @@
+.appointment-guide {
+  &-panel {
+    display: flex;
+    align-items: flex-end;
+    justify-content: space-between;
+    margin-bottom: 40px;
+    height: 459px;
+    background: #ffffff;
+    box-shadow: 0px 3px 31px 0px rgba(0, 0, 0, 0.1);
+    border-radius: 9px;
+
+    img {
+      display: block;
+    }
+    &__info {
+      width: 421px;
+      height: 100%;
+
+      h3 {
+        display: flex;
+        align-items: flex-end;
+        position: relative;
+        margin-bottom: 40px;
+        padding-left: 105px;
+        font-size: 23px;
+        color: #101010;
+        white-space: nowrap;
+        font-family: "SourceHanSans-Heavy";
+
+        img {
+          position: absolute;
+          top: -70px;
+          left: -30px;
+        }
+      }
+      p {
+        color: #101010;
+        font-size: 19px;
+        line-height: 33px;
+      }
+    }
+  }
+}

+ 173 - 3
src/views/Visit/AppointmentGuide/index.vue

@@ -1,3 +1,173 @@
-<template>
-  <div class="appointment-guide"></div>
-</template>
+<template>
+  <div class="appointment-guide">
+    <div
+      style="
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        height: 234px;
+      "
+    >
+      <Step />
+    </div>
+
+    <div class="appointment-guide-panel" style="padding: 0 65px 0 95px">
+      <div class="appointment-guide-panel__info" style="padding-top: 129px">
+        <h3
+          tabindex="0"
+          aria-description="Download the
+          WeChat app"
+        >
+          <img src="./images/number_1.1@2x-min.png" width="133px" />Download the
+          WeChat app
+        </h3>
+        <p
+          tabindex="0"
+          aria-description="You can search and download WeChat from the app store or visit the
+          official website of WeChat at https://www.wechat.com/en/"
+        >
+          You can search and download WeChat from the app store or visit the
+          official website of WeChat at https://www.wechat.com/en/
+        </p>
+      </div>
+
+      <img src="./images/img_step_1.1@2x-min.png" width="380px" />
+    </div>
+
+    <div
+      class="appointment-guide-panel"
+      style="padding: 0 83px 0 95px; height: 511px"
+    >
+      <img src="./images/img_step_1.2@2x-min.png" width="380px" />
+
+      <div class="appointment-guide-panel__info" style="padding-top: 129px">
+        <h3
+          style="padding-left: 125px"
+          tabindex="0"
+          aria-description="Create an account"
+        >
+          <img
+            src="./images/number_1.2@2x-min.png"
+            width="150px"
+            style="top: -65px; left: -25px"
+          />Create an account
+        </h3>
+        <p
+          tabindex="0"
+          aria-description="Fill in the necessary personal information, complete the
+          registration,and log in."
+        >
+          Fill in the necessary personal information, complete the
+          registration,and log in.
+        </p>
+      </div>
+    </div>
+
+    <div
+      class="appointment-guide-panel"
+      style="
+        padding: 0 75px;
+        flex-direction: column;
+        height: 500px;
+        align-items: flex-start;
+      "
+    >
+      <div class="appointment-guide-panel__info" style="padding-top: 90px">
+        <h3
+          tabindex="0"
+          aria-description="Scan the QR code to follow the official account"
+        >
+          <img
+            src="./images/number_2@2x-min.png"
+            width="100px"
+            style="top: -65px; left: 0"
+          />Scan the QR code to follow the official account
+        </h3>
+      </div>
+
+      <img src="./images/img_step_2@2x-min.png" width="100%" />
+    </div>
+
+    <div
+      class="appointment-guide-panel"
+      style="
+        padding: 0 75px;
+        flex-direction: column;
+        height: 668px;
+        align-items: flex-start;
+      "
+    >
+      <div
+        class="appointment-guide-panel__info"
+        style="padding-top: 90px; width: 100%"
+      >
+        <h3
+          tabindex="0"
+          aria-description="Make a personal appointment"
+          style="padding-left: 155px; margin-bottom: 30px"
+        >
+          <img
+            src="./images/number_3.1@2x-min.png"
+            width="153px"
+            style="top: -65px; left: 0"
+          />Make a personal appointment
+        </h3>
+        <p
+          tabindex="0"
+          aria-description="Apply for a personal appointment and register through your phone
+          number."
+        >
+          Apply for a personal appointment and register through your phone
+          number.
+        </p>
+      </div>
+
+      <img src="./images/img_step_3.1@2x-min.png" width="100%" />
+    </div>
+
+    <div
+      class="appointment-guide-panel"
+      style="
+        padding: 0 75px 20px;
+        flex-direction: column;
+        height: 1277px;
+        align-items: flex-start;
+      "
+    >
+      <div
+        class="appointment-guide-panel__info"
+        style="padding-top: 90px; width: 100%"
+      >
+        <h3
+          tabindex="0"
+          aria-description="Fill in the information"
+          style="padding-left: 185px; margin-bottom: 30px"
+        >
+          <img
+            src="./images/number_3.2@2x-min.png"
+            width="185px"
+            style="top: -65px; left: 0"
+          />Fill in the information
+        </h3>
+        <p
+          tabindex="0"
+          aria-description="Choose the date, fill in the necessary information, and complete the
+          appointment."
+        >
+          Choose the date, fill in the necessary information, and complete the
+          appointment.
+        </p>
+      </div>
+
+      <img src="./images/img_step_3.2@2x-min.png" width="100%" />
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import Step from "./components/Step.vue";
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 159 - 159
src/views/Visit/index.vue

@@ -1,159 +1,159 @@
-<template>
-  <div class="visit">
-    <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="visit-banner"
-      src="@/assets/images/Visit/m-4.jpg"
-    />
-
-    <div class="container">
-      <ul class="visit-nav">
-        <li
-          v-for="(item, index) in NAV_LIST"
-          :key="index"
-          :class="['visit-nav-item', { active: $route.name === item.pathName }]"
-          aria-label="Link"
-          :aria-description="item.name"
-          @click="$router.push({ name: item.pathName })"
-          @keydown.enter.passive="$router.push({ name: item.pathName })"
-        >
-          <div class="visit-nav-item__img">
-            <img :src="item.img" />
-          </div>
-          <p>
-            {{ item.name }}
-          </p>
-        </li>
-      </ul>
-
-      <Breadcrumb
-        :parents="[
-          {
-            label: 'Visit',
-            routeParams: { name: 'Visit' },
-          },
-        ]"
-        :cur-route="curRoute"
-      />
-
-      <RouterView />
-    </div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import Breadcrumb from "@/components/Breadcrumb/index.vue";
-import CalendarIcon from "@/assets/images/Visit/m-16.png";
-import DirectionIcon from "@/assets/images/Visit/m-9.png";
-import ReservationIcon from "@/assets/images/Visit/m-10.png";
-import PlansIcon from "@/assets/images/Visit/m-11.png";
-import GuideIcon from "@/assets/images/Visit/m-12.png";
-import AccessibilityIcon from "@/assets/images/Visit/m-13.png";
-import ShopIcon from "@/assets/images/Visit/m-14.png";
-import GuidelinesIcon from "@/assets/images/Visit/m-15.png";
-import AppointmentGuideIcon from "@/assets/images/Visit/m-17.png";
-import { useRoute } from "vue-router";
-import { computed } from "vue";
-
-const NAV_LIST = [
-  {
-    name: "Calendar",
-    img: CalendarIcon,
-    pathName: "Calendar",
-  },
-  {
-    name: "Appointment guide",
-    img: AppointmentGuideIcon,
-    pathName: "AppointmentGuide",
-  },
-  {
-    name: "Hours, Direction & Admission",
-    img: DirectionIcon,
-    pathName: "Direction",
-  },
-  { name: "Reservation", img: ReservationIcon, pathName: "Reservation" },
-  { name: "Floor Plans", img: PlansIcon, pathName: "Plans" },
-  {
-    name: "Audio Guide & Tour",
-    img: GuideIcon,
-    pathName: "Guide",
-  },
-  {
-    name: "Accessibility",
-    img: AccessibilityIcon,
-    pathName: "Accessibility",
-  },
-  { name: "Café & Shop", img: ShopIcon, pathName: "Shop" },
-  {
-    name: "Visitor Guidelines",
-    img: GuidelinesIcon,
-    pathName: "Guidelines",
-  },
-];
-
-const route = useRoute();
-const curRoute = computed(() =>
-  NAV_LIST.find((i) => i.pathName === (route.name as string))
-);
-</script>
-
-<style lang="scss" scoped>
-.visit {
-  &-banner {
-    margin-top: -60px;
-    display: block;
-    width: 100%;
-    height: 300px;
-    object-fit: cover;
-  }
-
-  &-nav {
-    position: relative;
-    margin-top: -15px;
-    padding: 5px 10px;
-    display: flex;
-    justify-content: center;
-    background: var(--white-bg);
-    border-radius: 5px;
-    z-index: 1;
-
-    li {
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      width: 168px;
-      height: 108px;
-      cursor: pointer;
-    }
-
-    &-item {
-      color: var(--black-text-color);
-
-      &.active {
-        color: var(--van-primary-color);
-
-        .visit-nav-item__img {
-          background: var(--van-primary-color);
-        }
-      }
-      &__img {
-        margin: 10px 0;
-        width: 50px;
-        height: 50px;
-        border-radius: 50%;
-        background: black;
-
-        img {
-          width: inherit;
-          height: inherit;
-        }
-      }
-      p {
-        font-size: 14px;
-        line-height: 18px;
-        text-align: center;
-      }
-    }
-  }
-}
-</style>
+<template>
+  <div class="visit">
+    <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="visit-banner"
+      src="@/assets/images/Visit/m-4.jpg"
+    />
+
+    <div class="container">
+      <ul class="visit-nav">
+        <li
+          v-for="(item, index) in NAV_LIST"
+          :key="index"
+          :class="['visit-nav-item', { active: $route.name === item.pathName }]"
+          aria-label="Link"
+          :aria-description="item.name"
+          @click="$router.push({ name: item.pathName })"
+          @keydown.enter.passive="$router.push({ name: item.pathName })"
+        >
+          <div class="visit-nav-item__img">
+            <img :src="item.img" />
+          </div>
+          <p>
+            {{ item.name }}
+          </p>
+        </li>
+      </ul>
+
+      <Breadcrumb
+        :parents="[
+          {
+            label: 'Visit',
+            routeParams: { name: 'Visit' },
+          },
+        ]"
+        :cur-route="curRoute"
+      />
+
+      <RouterView />
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import Breadcrumb from "@/components/Breadcrumb/index.vue";
+import CalendarIcon from "@/assets/images/Visit/m-16.png";
+import DirectionIcon from "@/assets/images/Visit/m-9.png";
+import ReservationIcon from "@/assets/images/Visit/m-10.png";
+import PlansIcon from "@/assets/images/Visit/m-11.png";
+import GuideIcon from "@/assets/images/Visit/m-12.png";
+import AccessibilityIcon from "@/assets/images/Visit/m-13.png";
+import ShopIcon from "@/assets/images/Visit/m-14.png";
+import GuidelinesIcon from "@/assets/images/Visit/m-15.png";
+import AppointmentGuideIcon from "@/assets/images/Visit/m-17.png";
+import { useRoute } from "vue-router";
+import { computed } from "vue";
+
+const NAV_LIST = [
+  {
+    name: "Calendar",
+    img: CalendarIcon,
+    pathName: "Calendar",
+  },
+  {
+    name: "Appointment guide",
+    img: AppointmentGuideIcon,
+    pathName: "AppointmentGuide",
+  },
+  {
+    name: "Hours, Direction & Admission",
+    img: DirectionIcon,
+    pathName: "Direction",
+  },
+  { name: "Reservation", img: ReservationIcon, pathName: "Reservation" },
+  { name: "Floor Plans", img: PlansIcon, pathName: "Plans" },
+  {
+    name: "Audio Guide & Tour",
+    img: GuideIcon,
+    pathName: "Guide",
+  },
+  {
+    name: "Accessibility",
+    img: AccessibilityIcon,
+    pathName: "Accessibility",
+  },
+  { name: "Café & Shop", img: ShopIcon, pathName: "Shop" },
+  {
+    name: "Visitor Guidelines",
+    img: GuidelinesIcon,
+    pathName: "Guidelines",
+  },
+];
+
+const route = useRoute();
+const curRoute = computed(() =>
+  NAV_LIST.find((i) => i.pathName === (route.name as string))
+);
+</script>
+
+<style lang="scss" scoped>
+.visit {
+  &-banner {
+    margin-top: -60px;
+    display: block;
+    width: 100%;
+    height: 300px;
+    object-fit: cover;
+  }
+
+  &-nav {
+    position: relative;
+    margin-top: -15px;
+    padding: 5px 0;
+    display: flex;
+    justify-content: center;
+    background: var(--white-bg);
+    border-radius: 5px;
+    z-index: 1;
+
+    li {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      width: 168px;
+      height: 108px;
+      cursor: pointer;
+    }
+
+    &-item {
+      color: var(--black-text-color);
+
+      &.active {
+        color: var(--van-primary-color);
+
+        .visit-nav-item__img {
+          background: var(--van-primary-color);
+        }
+      }
+      &__img {
+        margin: 10px 0;
+        width: 50px;
+        height: 50px;
+        border-radius: 50%;
+        background: black;
+
+        img {
+          width: inherit;
+          height: inherit;
+        }
+      }
+      p {
+        font-size: 14px;
+        line-height: 18px;
+        text-align: center;
+      }
+    }
+  }
+}
+</style>

+ 8 - 0
vite.config.ts

@@ -14,6 +14,14 @@ import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
 // https://vitejs.dev/config/
 export default defineConfig({
   base: "./",
+  server: {
+    proxy: {
+      "/api": {
+        target: "https://sit-shoubov2.4dage.com",
+        changeOrigin: true,
+      },
+    },
+  },
   css: {
     preprocessorOptions: {
       scss: {