chenlei 1 рік тому
батько
коміт
c40b09fc4b
66 змінених файлів з 7061 додано та 5192 видалено
  1. 0 0
      .env.production
  2. 1 0
      package.json
  3. 3120 2067
      pnpm-lock.yaml
  4. 40 0
      src/api/events.ts
  5. 21 1
      src/api/exhibition.ts
  6. 15 0
      src/api/index.ts
  7. 27 0
      src/api/search.ts
  8. 38 0
      src/api/visit.ts
  9. 4 0
      src/assets/css/base.css
  10. 1 0
      src/assets/svgs/404.svg
  11. 92 92
      src/components/FloatDirectory/index.vue
  12. 2 1
      src/components/Layout/index.vue
  13. 0 6
      src/configure.ts
  14. 37 0
      src/configure.tsx
  15. 345 15
      src/data.ts
  16. 2 0
      src/main.ts
  17. 28 5
      src/router/index.ts
  18. 55 0
      src/utils/date.ts
  19. 3 1
      src/utils/usePagination.ts
  20. 93 0
      src/views/404.vue
  21. 131 0
      src/views/About/Director.vue
  22. 16 0
      src/views/About/Info.vue
  23. 0 29
      src/views/About/Link.vue
  24. 70 65
      src/views/About/components/Director.vue
  25. BIN
      src/views/About/images/ren.jpg
  26. 16 12
      src/views/About/index.vue
  27. 1 1
      src/views/Collections/components/DetailDialog/index.vue
  28. 53 118
      src/views/Employment/index.vue
  29. 117 114
      src/views/Events/detail.scss
  30. 148 79
      src/views/Events/detail.vue
  31. 80 75
      src/views/Events/index.scss
  32. 109 59
      src/views/Events/index.vue
  33. 154 144
      src/views/Exhibitions/Detail/components/Galleries.vue
  34. 153 138
      src/views/Exhibitions/Detail/components/Objects.vue
  35. 205 119
      src/views/Exhibitions/Detail/components/Overview.vue
  36. 56 56
      src/views/Exhibitions/Detail/index.scss
  37. 75 25
      src/views/Exhibitions/Detail/index.vue
  38. 2 1
      src/views/Exhibitions/components/ListItem/index.vue
  39. 3 1
      src/views/Exhibitions/index.vue
  40. 2 2
      src/views/JoinSupport/Give/detail.vue
  41. 3 3
      src/views/JoinSupport/Volunteer/detail.vue
  42. 20 19
      src/views/JoinSupport/index.scss
  43. 0 3
      src/views/LearnEngage/Detail/index.scss
  44. 6 4
      src/views/LearnEngage/List/index.vue
  45. 139 129
      src/views/Search/index.scss
  46. 361 102
      src/views/Search/index.vue
  47. 74 74
      src/views/Use/index.scss
  48. 42 118
      src/views/Use/index.vue
  49. 32 32
      src/views/Visit/Accessibility/index.scss
  50. 17 72
      src/views/Visit/Accessibility/index.vue
  51. 6 6
      src/views/Visit/Calendar/components/Calendar.vue
  52. 6 1
      src/views/Visit/Calendar/components/DateTable/index.vue
  53. 216 214
      src/views/Visit/Calendar/index.scss
  54. 370 228
      src/views/Visit/Calendar/index.vue
  55. 32 32
      src/views/Visit/Guide/index.scss
  56. 17 72
      src/views/Visit/Guide/index.vue
  57. 43 36
      src/views/Visit/Plans/index.scss
  58. 87 87
      src/views/Visit/Plans/index.vue
  59. 10 54
      src/views/Visit/Reservation/components/Information.vue
  60. 9 277
      src/views/Visit/Reservation/components/Reservations.vue
  61. 10 98
      src/views/Visit/Reservation/components/Visit.vue
  62. 141 141
      src/views/Visit/Reservation/index.scss
  63. 37 70
      src/views/Visit/Reservation/index.vue
  64. 36 36
      src/views/Visit/Shop/index.scss
  65. 21 57
      src/views/Visit/Shop/index.vue
  66. 11 1
      src/views/Visit/index.vue

.env.production.development → .env.production


+ 1 - 0
package.json

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

Різницю між файлами не показано, бо вона завелика
+ 3120 - 2067
pnpm-lock.yaml


+ 40 - 0
src/api/events.ts

@@ -0,0 +1,40 @@
+import {
+  requestByGet,
+  requestByPost,
+  type PaginationParams,
+} from "@dage/service";
+
+export interface EventListParams extends PaginationParams {
+  searchKey?: string;
+}
+
+export interface EventListItem {
+  id: number;
+  name: string;
+  rtfTitle: string;
+  thumb: string;
+  dateStart: string;
+  dateEnd: string;
+}
+
+export interface EventDetail extends EventListItem {
+  rtf: string;
+}
+
+export const getEventListApi = (params: EventListParams) => {
+  return requestByPost("/api/show/event/pageList", params);
+};
+
+export const getEventDetailApi = (id: number | string) => {
+  return requestByGet<EventDetail>(`/api/show/event/detail/${id}`);
+};
+
+export interface RecommendEventListParams {
+  limit: number;
+  /** 需要排除的事件 id */
+  id: number | string;
+}
+
+export const getRecommendEventListApi = (params: RecommendEventListParams) => {
+  return requestByGet<EventDetail[]>("/api/show/event/recommend", params);
+};

+ 21 - 1
src/api/exhibition.ts

@@ -1,4 +1,9 @@
-import { requestByPost, type PaginationParams } from "@dage/service";
+import {
+  requestByGet,
+  requestByPost,
+  type PaginationParams,
+} from "@dage/service";
+import type { FileItem } from ".";
 
 export interface GetExhibitionListParams extends PaginationParams {
   /**
@@ -24,6 +29,21 @@ export interface ExhibitionsListItem {
   digest: string;
 }
 
+export interface ExhibitionDetail extends ExhibitionsListItem {
+  rtf: string;
+  thumbPc: string;
+  typeRemark: string;
+  address: string;
+  dateStart: string;
+  dateEnd: string;
+  exhibitsFile: FileItem[];
+  exhibitionFile: FileItem[];
+}
+
 export const getExhibitionListApi = (params: GetExhibitionListParams) => {
   return requestByPost("/api/show/exhibition/pageList", params);
 };
+
+export const getExhibitionDetailApi = (id: number | string) => {
+  return requestByGet<ExhibitionDetail>(`/api/show/exhibition/detail/${id}`);
+};

+ 15 - 0
src/api/index.ts

@@ -1,5 +1,17 @@
 import { requestByGet } from "@dage/service";
 
+export interface FileItem {
+  id: number;
+  filePath: string;
+  fileName: string;
+}
+
+export interface TxtArrItem {
+  id: number;
+  name: string;
+  txt: string;
+}
+
 export const getHomeListApi = () => {
   return requestByGet("/api/show/index/getList");
 };
@@ -16,3 +28,6 @@ export * from "./learn";
 export * from "./publications";
 export * from "./collections";
 export * from "./exhibition";
+export * from "./visit";
+export * from "./events";
+export * from "./search";

+ 27 - 0
src/api/search.ts

@@ -0,0 +1,27 @@
+import { requestByPost, type PaginationParams } from "@dage/service";
+
+export type ModuleType =
+  | "exhibition"
+  | "event"
+  | "collection"
+  | "learn"
+  | "publish";
+
+export interface SearchParams extends PaginationParams {
+  searchKey?: string;
+  module?: ModuleType;
+}
+
+export interface SearchItem {
+  id: number;
+  module: ModuleType;
+  thumb: string;
+  rtf: string;
+  name: string;
+  type: string;
+  filePath: string;
+}
+
+export const searchApi = (params: SearchParams) => {
+  return requestByPost("/api/show/search", params);
+};

+ 38 - 0
src/api/visit.ts

@@ -0,0 +1,38 @@
+import {
+  requestByGet,
+  requestByPost,
+  type PaginationParams,
+} from "@dage/service";
+
+export interface ListByDateItem {
+  /**
+   * 所属分类 id
+   */
+  id: number;
+  name: string;
+  thumb: string;
+  dateStart: string | null;
+  dateEnd: string | null;
+  module: string;
+  type: "learn" | "exhibition" | "event";
+  address: string;
+}
+
+export interface CalendarListParams extends PaginationParams {
+  module: "event" | "exhibition" | "learn" | "all";
+  startTime?: string;
+  endTime?: string;
+  searchKey?: string;
+}
+
+export const getListByDateApi = (params: { date: string }) => {
+  return requestByGet<ListByDateItem[]>("/api/show/findByDate", params);
+};
+
+export const getListByMonthApi = (params: { month: string }) => {
+  return requestByGet<ListByDateItem[]>("/api/show/findByMonth", params);
+};
+
+export const getCalendarListApi = (params: CalendarListParams) => {
+  return requestByPost("/api/show/recommend/pageList", params);
+};

+ 4 - 0
src/assets/css/base.css

@@ -235,3 +235,7 @@ a:hover {
   color: var(--gray-text-color);
   transform: translate(-50%, -50%);
 }
+
+.media-wrap {
+  text-align: center;
+}

Різницю між файлами не показано, бо вона завелика
+ 1 - 0
src/assets/svgs/404.svg


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

@@ -1,92 +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>
+<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: (typeof props.list)[0]["offsetTop"]) => {
+  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>

+ 2 - 1
src/components/Layout/index.vue

@@ -236,7 +236,7 @@ watch(
 const handleSearch = (txt: string) => {
   router.push({
     name: "Search",
-    query: { txt: txt },
+    query: { keyword: encodeURIComponent(txt) },
   });
   searchTxt2.value = "";
   searchTxt.value = "";
@@ -254,6 +254,7 @@ const handleSearch = (txt: string) => {
   --white-bg: white;
   --white-text-color: white;
   font-family: "SourceHanSans-Regular";
+  min-width: 1200px;
 
   &.white {
     --topnav-bg-color: white;

+ 0 - 6
src/configure.ts

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

+ 37 - 0
src/configure.tsx

@@ -0,0 +1,37 @@
+import { compose, initial } from "@dage/service";
+import { ElMessage } from "element-plus";
+import "element-plus/theme-chalk/el-message.css";
+
+declare global {
+  interface DageRequestMeta {
+    /**
+     * 显示全局 错误信息, 默认为 true
+     */
+    showError?: boolean;
+  }
+}
+
+const showMessage = (msg: string, type = "error") => {
+  ElMessage({
+    type,
+    // @ts-ignore
+    message: msg,
+    duration: 4000,
+  });
+};
+
+initial({
+  fetch: window.fetch.bind(window),
+  baseURL: import.meta.env.VITE_BASE_URL,
+  interceptor: compose(async (request, next) => {
+    const response = await next();
+    const { showError = true } = request.meta;
+
+    if (response.code !== 0 && showError) {
+      const message = response.__raw__.data.msg ?? "System on business trip";
+      showMessage(message);
+    }
+
+    return response;
+  }),
+});

Різницю між файлами не показано, бо вона завелика
+ 345 - 15
src/data.ts


+ 2 - 0
src/main.ts

@@ -3,6 +3,7 @@ import "./configure";
 
 import { createApp } from "vue";
 import { createPinia } from "pinia";
+import { MotionPlugin } from "@vueuse/motion";
 
 import App from "./App.vue";
 import router from "./router";
@@ -16,6 +17,7 @@ const app = createApp(App);
 app.use(createPinia());
 app.use(router);
 app.use(eventBus);
+app.use(MotionPlugin);
 app.component("svg-icon", svgIcon);
 
 app.mount("#app");

+ 28 - 5
src/router/index.ts

@@ -161,12 +161,25 @@ const router = createRouter({
         {
           path: "/Layout/About",
           name: "About",
+          redirect: "/Layout/About/index",
           component: () => import("../views/About/index.vue"),
-        },
-        {
-          path: "/Layout/About/link",
-          name: "AboutLink",
-          component: () => import("../views/About/Link.vue"),
+          children: [
+            {
+              path: "/Layout/About/index",
+              name: "AboutIndex",
+              component: () => import("../views/About/Info.vue"),
+            },
+            {
+              path: "/Layout/About/link",
+              name: "AboutLink",
+              component: () => import("../views/About/Link.vue"),
+            },
+            {
+              path: "/Layout/About/Director",
+              name: "AboutDirector",
+              component: () => import("../views/About/Director.vue"),
+            },
+          ],
         },
         {
           path: "/Layout/Search",
@@ -198,8 +211,18 @@ const router = createRouter({
           name: "Employment",
           component: () => import("../views/Employment/index.vue"),
         },
+        {
+          path: "/Layout/404",
+          name: "404",
+          component: () => import("../views/404.vue"),
+        },
       ],
     },
+    {
+      path: "/:pathMatch(.*)",
+      name: "error",
+      redirect: "/layout/404",
+    },
   ],
 });
 

+ 55 - 0
src/utils/date.ts

@@ -0,0 +1,55 @@
+export const MONTHS = [
+  "January",
+  "February",
+  "March",
+  "April",
+  "May",
+  "June",
+  "July",
+  "August",
+  "September",
+  "October",
+  "November",
+  "December",
+];
+
+export const DAYS_OF_WEEK = [
+  "Sunday",
+  "Monday",
+  "Tuesday",
+  "Wednesday",
+  "Thursday",
+  "Friday",
+  "Saturday",
+];
+
+/**
+ * 格式化日期
+ * @example
+ * getFormatDate('2024-07-05', null) // June 5, 2024
+ */
+export const getFormatDate = (dateStart: string, dateEnd?: string | null) => {
+  let dateTime = "";
+  const start = new Date(dateStart);
+  const end = dateEnd ? new Date(dateEnd) : null;
+
+  if (end) {
+    if (start.getFullYear() === end.getFullYear()) {
+      dateTime = `${MONTHS[start.getDay()]} ${start.getDate()} - ${
+        MONTHS[end.getDay()]
+      } ${end.getDate()}, ${end.getFullYear()}`;
+    } else {
+      dateTime = `${
+        MONTHS[start.getDay()]
+      } ${start.getDate()}, ${start.getFullYear()} - ${
+        MONTHS[end.getDay()]
+      } ${end.getDate()}, ${end.getFullYear()}`;
+    }
+  } else {
+    dateTime = `${
+      MONTHS[start.getDay()]
+    } ${start.getDate()}, ${start.getFullYear()}`;
+  }
+
+  return dateTime;
+};

+ 3 - 1
src/utils/usePagination.ts

@@ -5,10 +5,12 @@ export enum PaginationType {
   CONCAT = 1,
 }
 
+export const DEFAULT_PAGE_SIZE = 10;
+
 export const usePagination = <T>(
   handler: (params: { pageNum: number; pageSize: number }) => any,
   type = PaginationType.DEFAULT,
-  size = 10
+  size = DEFAULT_PAGE_SIZE
 ) => {
   const pageNum = ref(1);
   const total = ref(0);

+ 93 - 0
src/views/404.vue

@@ -0,0 +1,93 @@
+<script setup lang="ts">
+import { useRouter } from "vue-router";
+
+defineOptions({
+  name: "404",
+});
+
+const router = useRouter();
+</script>
+
+<template>
+  <div class="error-page">
+    <SvgIcon name="404" size="300" />
+    <div class="error-page-inner">
+      <p
+        v-motion
+        :initial="{
+          opacity: 0,
+          y: 100,
+        }"
+        :enter="{
+          opacity: 1,
+          y: 0,
+          transition: {
+            delay: 80,
+          },
+        }"
+        class="error-page-inner__title"
+      >
+        404
+      </p>
+      <p
+        v-motion
+        class="error-page-inner__subtitle"
+        :initial="{
+          opacity: 0,
+          y: 100,
+        }"
+        :enter="{
+          opacity: 1,
+          y: 0,
+          transition: {
+            delay: 120,
+          },
+        }"
+      >
+        Sorry, the page you visited does not exist
+      </p>
+      <el-button
+        v-motion
+        type="primary"
+        :initial="{
+          opacity: 0,
+          y: 100,
+        }"
+        :enter="{
+          opacity: 1,
+          y: 0,
+          transition: {
+            delay: 160,
+          },
+        }"
+        @click="router.push('/')"
+      >
+        Back home
+      </el-button>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.error-page {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 640px;
+
+  &-inner {
+    margin: 12px 0;
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+    text-align: center;
+    color: #666;
+
+    p:first-child {
+      font-size: 30px;
+      font-weight: bold;
+    }
+  }
+}
+</style>

+ 131 - 0
src/views/About/Director.vue

@@ -0,0 +1,131 @@
+<template>
+  <div
+    class="about-director container"
+    tabindex="0"
+    data-aria-viewport-area
+    aria-description="You've reached the content area of the From the Director page, please use the tab key to navigate through the content."
+  >
+    <p class="about-director__title">{{ About.Director.name }}</p>
+
+    <div class="about-director-info">
+      <p><span>Leadership</span></p>
+
+      <div class="about-director-card">
+        <img src="./images/ren.jpg" />
+
+        <p>Han Zhanming</p>
+        <span>Director of Capital Museum</span>
+      </div>
+    </div>
+
+    <div class="about-director-inner" v-html="About.Director.rtf" />
+  </div>
+
+  <div
+    tabindex="0"
+    aria-label="Link"
+    class="about-director-btn"
+    @click="$router.go(-1)"
+  >
+    Back
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { About } from "@/data";
+</script>
+
+<style lang="scss" scoped>
+.about-director {
+  background: var(--white-bg);
+  border: 1px solid #e0e0e0;
+
+  &-btn {
+    font-weight: 700;
+    font-size: 16px;
+    line-height: 38px;
+    background: #f1f1f1;
+    border: 1px solid #000;
+    text-align: center;
+    display: block;
+    width: 160px;
+    height: 38px;
+    margin: 30px auto;
+    cursor: pointer;
+  }
+  &__title {
+    font-size: 35px;
+    line-height: 118px;
+    text-align: center;
+  }
+  &-inner {
+    padding: 40px;
+    font-size: 18px;
+    line-height: 26px;
+
+    :deep(p) {
+      margin-bottom: 30px;
+    }
+  }
+  &-card {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    margin: 0 auto;
+    padding: 30px 0 40px;
+    width: 1120px;
+    background: #f1f1f1;
+    border: 1px solid #e0e0e0;
+
+    img {
+      width: 170px;
+      height: 170px;
+      border: 5px solid #fff;
+      border-radius: 50%;
+    }
+    p {
+      font-weight: bold;
+      font-size: 36px;
+      line-height: 200%;
+    }
+    span {
+      font-weight: bold;
+      font-size: 18px;
+      color: var(--van-primary-color);
+      line-height: 200%;
+    }
+  }
+  &-info {
+    padding-bottom: 40px;
+    border: 1px solid #e0e0e0;
+    border-left-width: 0;
+    border-right-width: 0;
+
+    > p {
+      position: relative;
+      font-size: 22px;
+      line-height: 118px;
+      text-align: center;
+
+      span {
+        position: relative;
+        padding: 0 20px;
+        color: var(--van-primary-color);
+        background: var(--white-bg);
+        z-index: 2;
+      }
+      &::after {
+        content: "";
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        width: 240px;
+        height: 2px;
+        background: #939393;
+        transform: translate(-50%, -50%);
+      }
+    }
+  }
+}
+</style>

+ 16 - 0
src/views/About/Info.vue

@@ -0,0 +1,16 @@
+<template>
+  <Director />
+
+  <History />
+
+  <Connections />
+
+  <Contact />
+</template>
+
+<script setup lang="ts">
+import Director from "./components/Director.vue";
+import History from "./components/History.vue";
+import Connections from "./components/Connections.vue";
+import Contact from "./components/Contact.vue";
+</script>

+ 0 - 29
src/views/About/Link.vue

@@ -1,24 +1,7 @@
 <template>
   <div class="about-link">
-    <img
-      class="about-link-banner"
-      aria-label="Image"
-      aria-description="You've reached the banner area of the About page; this page has one image; please use the tab key to go through the content."
-      :src="bannerUrl"
-    />
-
     <div style="background: var(--white-bg)">
       <div class="container">
-        <Breadcrumb
-          :parents="[
-            {
-              label: 'About',
-              routeParams: { name: 'About' },
-            },
-          ]"
-          :cur-route="{ name: 'Link' }"
-        />
-
         <div
           class="about-link-title"
           tabindex="0"
@@ -72,19 +55,7 @@
 </template>
 
 <script lang="ts" setup>
-import Breadcrumb from "@/components/Breadcrumb/index.vue";
-import { useBaseStore } from "@/stores/base";
-import { getBaseURL } from "@dage/service";
-import { computed } from "vue";
 import { linkData } from "./data";
-
-const baseUrl = getBaseURL();
-const baseStore = useBaseStore();
-const bannerUrl = computed(() => {
-  return (
-    baseUrl + baseStore.bannerList.find((i) => i.name === "About")?.thumbPc
-  );
-});
 </script>
 
 <style lang="scss" scoped>

+ 70 - 65
src/views/About/components/Director.vue

@@ -1,65 +1,70 @@
-<template>
-  <div
-    class="about-director"
-    tabindex="0"
-    data-aria-viewport-area
-    aria-description="You've reached the From the Director section, please use the tab key to navigate through the content."
-  >
-    <div class="about-director__container">
-      <p tabindex="0">Welcome to the website of the Capital Museum of China.</p>
-      <p tabindex="0">
-        We are looking forward to your visit. The Capital Museum is a palace of
-        Beijing culture. Its collections relate to the long development of the
-        capital city, showcasing its magnificent living history of 500,000
-        years, urban history of 3,000 years and history as the Chinese capital
-        for 800 years. The museum also presents cultural and artistic
-        exhibitions from different regions and displays the achievements of
-        various ethnic groups at home and abroad. You can feel the breadth of
-        both Chinese and world civilizations.
-      </p>
-      <p tabindex="0">
-        Historical culture is the soul of a city and in the case of Beijing it
-        is a great witness to the long history of Chinese civilization. We are
-        looking forward to welcoming you to share with us the full
-        interpretation of this glorious civilization while immersing yourself in
-        the great wisdom of Beijing.<br />
-      </p>
-      <div class="about-director__more" tabindex="0" aria-label="Link">
-        Read More
-      </div>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-.about-director {
-  padding: 135px 0 20px;
-  height: 466px;
-  color: white;
-  font-size: 16px;
-  line-height: 20px;
-  background: url("../images/au2.jpg") rgb(203, 7, 7) no-repeat center top;
-
-  &__container {
-    width: 750px;
-    margin: 0px auto;
-    padding-left: 50px;
-
-    p {
-      text-align: justify;
-      padding-bottom: 13px;
-    }
-  }
-  &__more {
-    margin: 20px auto;
-    width: 110px;
-    font-size: 12px;
-    color: white;
-    line-height: 30px;
-    text-align: center;
-    border: 1px solid rgb(255, 255, 255);
-    font-weight: bold;
-    cursor: pointer;
-  }
-}
-</style>
+<template>
+  <div
+    class="about-director"
+    tabindex="0"
+    data-aria-viewport-area
+    aria-description="You've reached the From the Director section, please use the tab key to navigate through the content."
+  >
+    <div class="about-director__container">
+      <p tabindex="0">Welcome to the website of the Capital Museum of China.</p>
+      <p tabindex="0">
+        We are looking forward to your visit. The Capital Museum is a palace of
+        Beijing culture. Its collections relate to the long development of the
+        capital city, showcasing its magnificent living history of 500,000
+        years, urban history of 3,000 years and history as the Chinese capital
+        for 800 years. The museum also presents cultural and artistic
+        exhibitions from different regions and displays the achievements of
+        various ethnic groups at home and abroad. You can feel the breadth of
+        both Chinese and world civilizations.
+      </p>
+      <p tabindex="0">
+        Historical culture is the soul of a city and in the case of Beijing it
+        is a great witness to the long history of Chinese civilization. We are
+        looking forward to welcoming you to share with us the full
+        interpretation of this glorious civilization while immersing yourself in
+        the great wisdom of Beijing.<br />
+      </p>
+      <div
+        class="about-director__more"
+        tabindex="0"
+        aria-label="Link"
+        @click="$router.push({ name: 'AboutDirector' })"
+      >
+        Read More
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.about-director {
+  padding: 135px 0 20px;
+  height: 466px;
+  color: white;
+  font-size: 16px;
+  line-height: 20px;
+  background: url("../images/au2.jpg") rgb(203, 7, 7) no-repeat center top;
+
+  &__container {
+    width: 750px;
+    margin: 0px auto;
+    padding-left: 50px;
+
+    p {
+      text-align: justify;
+      padding-bottom: 13px;
+    }
+  }
+  &__more {
+    margin: 20px auto;
+    width: 110px;
+    font-size: 12px;
+    color: white;
+    line-height: 30px;
+    text-align: center;
+    border: 1px solid rgb(255, 255, 255);
+    font-weight: bold;
+    cursor: pointer;
+  }
+}
+</style>

BIN
src/views/About/images/ren.jpg


+ 16 - 12
src/views/About/index.vue

@@ -8,25 +8,15 @@
     />
 
     <div class="container">
-      <Breadcrumb :parents="[]" :cur-route="{ name: 'About' }" />
+      <Breadcrumb :parents="parentRoutes" :cur-route="curRoute" />
     </div>
 
-    <Director />
-
-    <History />
-
-    <Connections />
-
-    <Contact />
+    <RouterView />
   </div>
 </template>
 
 <script lang="ts" setup>
 import Breadcrumb from "@/components/Breadcrumb/index.vue";
-import Director from "./components/Director.vue";
-import History from "./components/History.vue";
-import Connections from "./components/Connections.vue";
-import Contact from "./components/Contact.vue";
 import { useRoute } from "vue-router";
 import { watch, nextTick, computed } from "vue";
 import { useBaseStore } from "@/stores/base";
@@ -40,6 +30,20 @@ const bannerUrl = computed(() => {
     baseUrl + baseStore.bannerList.find((i) => i.name === "About")?.thumbPc
   );
 });
+const parentRoutes = computed(() => {
+  if (route.name === "AboutIndex") return [];
+
+  return [
+    {
+      label: "About",
+      routeParams: { name: "About" },
+    },
+  ];
+});
+const curRoute = computed(() => {
+  if (route.name === "AboutIndex") return { name: "About" };
+  if (route.name === "AboutLink") return { name: "Partners & Connections" };
+});
 
 watch(
   route,

+ 1 - 1
src/views/Collections/components/DetailDialog/index.vue

@@ -72,7 +72,7 @@ const show = computed({
   },
 });
 
-let timer: number;
+let timer: NodeJS.Timeout;
 const maskRef = ref();
 const loading = ref(false);
 const rtf = ref<{ id: number; txt: string }[]>([]);

+ 53 - 118
src/views/Employment/index.vue

@@ -1,118 +1,53 @@
-<template>
-  <div class="use-page">
-    <Breadcrumb :parents="[]" :cur-route="{ name: 'Employment' }" />
-
-    <div class="container use-page-panel">
-      <div class="use-page-panel-hd">
-        <h3>Terms of Use</h3>
-
-        <div class="use-page-panel-hd__ft">
-          <img src="@/assets/images/bg_5.png" />
-          <div class="date">2018</div>
-
-          <img src="@/assets/images/bg_7.png" />
-          <div class="address">Capital Museum</div>
-        </div>
-      </div>
-
-      <div class="use-page-panel-main">
-        <div class="use-page-panel__title">
-          <span>Exhibition Overview</span>
-        </div>
-
-        <p tabindex="0">Legal Notice</p>
-        <p tabindex="0">
-          Welcome to www.capitalmuseum.org.cn (hereinafter referred to as "this
-          website"). You are expected to comply with following terms while using
-          this website:
-        </p>
-        <p tabindex="0">
-          Copyright of all contents on this website, including but not limited
-          to the website design, texts, pictures, audio and video works, etc.,
-          belong to the Capital Museum and all the parties concerned. All media,
-          websites, organizations or individuals are allowed to read these
-          contents, but cannot use them in the ways including but not limited to
-          reprinting, excerpting, linking, reposting, publication, transfer,
-          distribution, etc. And unauthorized use of this website and its
-          contents for commercial purposes is also prohibited.
-        </p>
-        <p tabindex="0">
-          If you download materials you consider you need from this website for
-          non-commercial use (except for information with copyrights and
-          proprietary rights), please contact us for permission.
-        </p>
-        <p tabindex="0">
-          Any reprints and quotes of any copyrighted articles on this website
-          should conform to following requirements:
-        </p>
-        <p
-          tabindex="0"
-          :aria-description="`(1) For non-commercial, non-profit and non-advertising uses, author's name and source of the used article or picture, &quot;Capital Museum Website&quot; or &quot;${$homePageUrl}&quot;, should be attached.`"
-        >
-          (1) For non-commercial, non-profit and non-advertising uses, author's
-          name and source of the used article or picture, "首都博物馆网站" or
-          "{{ $homePageUrl }}", should be attached.
-        </p>
-        <p
-          tabindex="0"
-          :aria-description="`(2) For business, profit-making and advertising use, you should obtain consent of the original author, and attach the name of that author, limits of authority and the source, &quot;Capital Museum Website&quot;or &quot;${$homePageUrl}&quot;.`"
-        >
-          (2) For business, profit-making and advertising use, you should obtain
-          consent of the original author, and attach the name of that author,
-          limits of authority and the source, "首都博物馆网站"or "{{
-            $homePageUrl
-          }}".
-        </p>
-        <p tabindex="0">
-          (3) Any modification and cancellation of any articles or pictures
-          should be approved by the author, with the limits of authority
-          attached.
-        </p>
-        <p tabindex="0">
-          This website and the Capital Museum are not liable for any direct or
-          indirect incidental damage resulting from the use or inability to use
-          the information on this website. This website also does not assume any
-          criminal or civil liability arising from any violation of the
-          provisions of this website or the laws of the People's Republic of
-          China.
-        </p>
-        <p tabindex="0">
-          This website will make announcement in advance if services are to be
-          suspended due to system maintenance or upgrading. The website and the
-          Capital Museum are not be liable for any inconvenience or losses
-          caused by the suspension of services due to hardware failure or force
-          majeure.
-        </p>
-        <p tabindex="0">
-          The right of final interpretation of all contents of this website is
-          owned by the Capital Museum.
-        </p>
-        <p tabindex="0">
-          To safeguard rights and interests of this website and respect for the
-          authors' copyrights, we entrust Lyu Xiaojing of the Beijing Realizer
-          Law Firm as the legal adviser to this website. Anyone violating the
-          terms of this notice and laws will be held accountable. If any
-          contents published and reposted on this website violate your
-          copyrights, please contact our lawyer within two weeks.
-        </p>
-        <p tabindex="0">The Capital Museum</p>
-      </div>
-    </div>
-
-    <button class="use-page__back" @click="backTop">Back to top</button>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import Breadcrumb from "@/components/Breadcrumb/index.vue";
-
-const $homePageUrl = location.origin;
-
-const backTop = () => {
-  window.scrollTo({ top: 0, behavior: "smooth" });
-};
-</script>
-
-<style lang="scss" scoped>
-@import "../Use/index.scss";
-</style>
+<template>
+  <div class="use-page">
+    <Breadcrumb :parents="[]" :cur-route="{ name: 'Employment' }" />
+
+    <div class="container use-page-panel">
+      <div class="use-page-panel-hd">
+        <h3>
+          {{ Employment.name }}
+        </h3>
+
+        <div class="use-page-panel-hd__ft">
+          <img src="@/assets/images/bg_5.png" />
+          <div class="date">2018</div>
+
+          <img src="@/assets/images/bg_7.png" />
+          <div class="address">Capital Museum</div>
+        </div>
+      </div>
+
+      <div class="use-page-panel-main">
+        <div class="use-page-panel__title">
+          <span>Exhibition Overview</span>
+        </div>
+
+        <div v-html="Employment.rtf" />
+      </div>
+    </div>
+
+    <button class="use-page__back" @click="backTop">Back to top</button>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import Breadcrumb from "@/components/Breadcrumb/index.vue";
+import { Employment } from "@/data";
+
+const backTop = () => {
+  window.scrollTo({ top: 0, behavior: "smooth" });
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../Use/index.scss";
+
+.use-page-panel-main {
+  :deep(p) {
+    span {
+      display: block;
+      font-weight: bold;
+    }
+  }
+}
+</style>

+ 117 - 114
src/views/Events/detail.scss

@@ -1,114 +1,117 @@
-.events-detail {
-  &__banner {
-    margin-top: -20px;
-    height: 450px;
-    object-fit: cover;
-  }
-  &__main {
-    position: relative;
-    margin: -240px auto 0;
-    width: 900px;
-    z-index: 1;
-
-    .breadcrumb {
-      padding: 0 30px;
-      background: var(--white-bg);
-    }
-  }
-  &-hd {
-    display: flex;
-    background-color: #f4f4f4;
-
-    &__info {
-      flex: 1;
-      font-weight: 700;
-      font-family: helvetica;
-      font-size: 36px;
-      line-height: 36px;
-      padding: 40px 30px 20px 30px;
-
-      > p {
-        color: var(--van-primary-color);
-      }
-      &__container {
-        margin-top: 15px;
-        font-family: Georgia;
-        font-size: 16px;
-        line-height: 20px;
-
-        p {
-          margin: 0 auto;
-          width: 350px;
-          text-align: left;
-          font-weight: 500;
-          cursor: pointer;
-        }
-      }
-    }
-    &__date {
-      flex-shrink: 0;
-      width: 294px;
-      font-family: Georgia;
-      font-size: 30px;
-      color: #fff;
-      line-height: 30px;
-      padding: 40px 20px;
-      background-color: #353535;
-
-      p:first-child {
-        margin-bottom: 30px;
-        font-size: 36px;
-        line-height: 40px;
-      }
-    }
-  }
-  &-container {
-    padding: 40px 30px;
-    font-family: arial, helvetica, sans-serif;
-    font-size: 16px;
-    line-height: 2em;
-    text-align: justify;
-    background: var(--white-bg);
-    box-shadow: 0 5px 5px rgba(0, 0, 0, 0.5);
-  }
-  &-other {
-    margin: 60px 0;
-
-    h3 {
-      margin-bottom: 20px;
-      font-weight: 700;
-      font-size: 30px;
-      line-height: 54px;
-      font-family: Helvetica;
-      text-align: center;
-      border-bottom: 5px solid var(--van-primary-color);
-    }
-    ul {
-      display: flex;
-      margin: -15px;
-
-      li {
-        margin: 15px;
-        width: calc(50% - 30px);
-        background: var(--white-bg);
-
-        .el-image {
-          height: 210px;
-        }
-        p {
-          font-weight: 700;
-          font-family: Helvetica;
-          font-size: 24px;
-          line-height: 30px;
-          padding: 30px 20px;
-        }
-      }
-    }
-  }
-  &__backtop {
-    position: fixed;
-    right: 140px;
-    bottom: 100px;
-    cursor: pointer;
-  }
-}
+.events-detail {
+  &__banner {
+    width: 100%;
+    height: 450px;
+    object-fit: cover;
+  }
+  &__main {
+    position: relative;
+    margin: -240px auto 0;
+    width: 900px;
+    z-index: 1;
+
+    .breadcrumb {
+      padding: 0 30px;
+      background: var(--white-bg);
+    }
+  }
+  &-hd {
+    display: flex;
+    background-color: #f4f4f4;
+
+    &__info {
+      flex: 1;
+      font-weight: 700;
+      font-family: helvetica;
+      font-size: 36px;
+      line-height: 36px;
+      padding: 40px 30px 20px 30px;
+
+      > p {
+        color: var(--van-primary-color);
+      }
+      &__container {
+        margin-top: 15px;
+        font-family: Georgia;
+        font-size: 16px;
+        line-height: 20px;
+
+        p {
+          margin: 0 auto;
+          width: 350px;
+          text-align: left;
+          font-weight: 500;
+          cursor: pointer;
+        }
+      }
+    }
+    &__date {
+      flex-shrink: 0;
+      width: 294px;
+      font-family: Georgia;
+      font-size: 30px;
+      color: #fff;
+      line-height: 30px;
+      padding: 40px 20px;
+      background-color: #353535;
+
+      p:first-child {
+        margin-bottom: 30px;
+        font-size: 36px;
+        line-height: 40px;
+      }
+    }
+  }
+  &-container {
+    margin-bottom: 60px;
+    padding: 40px 30px;
+    font-family: arial, helvetica, sans-serif;
+    font-size: 16px;
+    line-height: 2em;
+    text-align: justify;
+    background: var(--white-bg);
+    box-shadow: 0 5px 5px rgba(0, 0, 0, 0.5);
+  }
+  &-other {
+    margin-bottom: 60px;
+
+    h3 {
+      margin-bottom: 20px;
+      font-weight: 700;
+      font-size: 30px;
+      line-height: 54px;
+      font-family: Helvetica;
+      text-align: center;
+      border-bottom: 5px solid var(--van-primary-color);
+    }
+    ul {
+      display: flex;
+      margin: -15px;
+
+      li {
+        margin: 15px;
+        width: calc(50% - 30px);
+        background: var(--white-bg);
+        cursor: pointer;
+
+        .el-image {
+          height: 210px;
+        }
+        p {
+          font-weight: 700;
+          font-family: Helvetica;
+          font-size: 24px;
+          line-height: 30px;
+          margin: 30px 20px;
+        }
+      }
+    }
+  }
+  &__backtop {
+    position: fixed;
+    right: 140px;
+    bottom: 100px;
+    cursor: pointer;
+    z-index: 9;
+  }
+}

+ 148 - 79
src/views/Events/detail.vue

@@ -1,79 +1,148 @@
-<template>
-  <div class="events-detail container">
-    <img
-      class="events-detail__banner"
-      src="https://en.capitalmuseum.org.cn/data/About/Events/2Ac.jpg"
-      alt=""
-      tabindex=""
-      aria-description=""
-    />
-
-    <div class="events-detail__main">
-      <Breadcrumb
-        :parents="[{ label: 'Events', routeParams: { name: 'Events' } }]"
-      />
-
-      <div class="events-detail-hd">
-        <div class="events-detail-hd__info">
-          <h3 tabindex="0">
-            Development of Contemporary Museum and Exhibition Interpretation
-          </h3>
-          <p tabindex="0">International Symposium</p>
-
-          <div class="events-detail-hd__info__container">
-            <p tabindex="0">■ Introduction</p>
-            <p tabindex="0">
-              ■ Directors' Roundtable of Development of Contemporary Museums and
-              Exhibition Interpretation
-            </p>
-            <p tabindex="0">
-              ■ Development of a Contemporary Museum and Exhibition
-              Interpretation
-            </p>
-          </div>
-        </div>
-
-        <div class="events-detail-hd__date">
-          <p tabindex="0">Date:</p>
-          <p tabindex="0">October 27 - October 28, 2019</p>
-        </div>
-      </div>
-
-      <div class="events-detail-container"></div>
-
-      <div class="events-detail-other">
-        <h3>View Other Events</h3>
-
-        <ul>
-          <li>
-            <ElImage
-              fit="cover"
-              src="https://en.capitalmuseum.org.cn/data/About/Events/3.jpg"
-            />
-            <p>A Bright New Year Opens for Capital Museum</p>
-          </li>
-        </ul>
-      </div>
-    </div>
-
-    <img
-      class="events-detail__backtop"
-      src="./images/toTop.jpg"
-      aria-label="Button"
-      aria-description="Back to top"
-      @click="toTop"
-    />
-  </div>
-</template>
-
-<script lang="ts" setup>
-import Breadcrumb from "@/components/Breadcrumb/index.vue";
-
-const toTop = () => {
-  window.scrollTo({ top: 0, behavior: "smooth" });
-};
-</script>
-
-<style lang="scss" scoped>
-@import "./detail.scss";
-</style>
+<template>
+  <div v-loading="loading" class="events-detail container">
+    <img class="events-detail__banner" :src="baseUrl + detail?.thumb" />
+
+    <div class="events-detail__main">
+      <Breadcrumb
+        :parents="[{ label: 'Events', routeParams: { name: 'Events' } }]"
+        :cur-route="{
+          name: detail?.name || '',
+        }"
+      />
+
+      <div class="events-detail-hd">
+        <div class="events-detail-hd__info">
+          <h3 tabindex="0">
+            {{ detail?.name }}
+          </h3>
+          <!-- <p tabindex="0">International Symposium</p> -->
+
+          <div class="events-detail-hd__info__container">
+            <p
+              v-for="(title, idx) in detail?.titleArr"
+              :key="title"
+              tabindex="0"
+              @click="
+                () => {
+                  tabIndex = idx;
+                  $router.replace({
+                    name: 'EventsDetail',
+                    params: {
+                      id: idx === 0 ? detail?.id : `${detail?.id}.${idx}`,
+                    },
+                  });
+                }
+              "
+            >
+              ■ {{ title }}
+            </p>
+          </div>
+        </div>
+
+        <div class="events-detail-hd__date">
+          <p tabindex="0">Date:</p>
+          <p v-if="detail" tabindex="0">
+            {{ getFormatDate(detail.dateStart, detail.dateEnd) }}
+          </p>
+        </div>
+      </div>
+
+      <div
+        class="events-detail-container"
+        v-html="detail?.txtArr[tabIndex].txt"
+      />
+
+      <div v-if="recommendList.length" class="events-detail-other">
+        <h3>View Other Events</h3>
+
+        <ul>
+          <li
+            v-for="item in recommendList"
+            :key="item.id"
+            @click="
+              $router.push({ name: 'EventsDetail', params: { id: item.id } })
+            "
+          >
+            <ElImage fit="cover" :src="baseUrl + item.thumb" />
+            <p class="limit-line line-2">{{ item.name }}</p>
+          </li>
+        </ul>
+      </div>
+    </div>
+
+    <img
+      class="events-detail__backtop"
+      src="./images/toTop.jpg"
+      aria-label="Button"
+      aria-description="Back to top"
+      @click="toTop"
+    />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {
+  getEventDetailApi,
+  getRecommendEventListApi,
+  type EventDetail,
+  type EventListItem,
+  type TxtArrItem,
+} from "@/api";
+import Breadcrumb from "@/components/Breadcrumb/index.vue";
+import { getFormatDate } from "@/utils/date";
+import { getBaseURL } from "@dage/service";
+import { onMounted, ref } from "vue";
+import { useRoute } from "vue-router";
+
+interface PrivateEventDetail extends EventDetail {
+  txtArr: TxtArrItem[];
+  titleArr: string[];
+}
+
+const baseUrl = getBaseURL();
+const route = useRoute();
+const loading = ref(false);
+const tabIndex = ref(0);
+const detail = ref<PrivateEventDetail | null>(null);
+const recommendList = ref<EventListItem[]>([]);
+
+onMounted(() => {
+  getDetail();
+  getRecommendList();
+});
+
+const getDetail = async () => {
+  try {
+    loading.value = true;
+    const routeId = (route.params.id as string).split(".");
+    const data = await getEventDetailApi(routeId[0]);
+
+    detail.value = {
+      ...data,
+      titleArr: JSON.parse(data.rtfTitle),
+      txtArr: data.rtf ? JSON.parse(data.rtf).txtArr : [],
+    };
+
+    if (routeId.length > 1) tabIndex.value = Number(routeId[1]);
+  } finally {
+    loading.value = false;
+  }
+};
+
+const getRecommendList = async () => {
+  const routeId = (route.params.id as string).split(".");
+  const data = await getRecommendEventListApi({
+    limit: 4,
+    id: routeId[0],
+  });
+  recommendList.value = data;
+};
+
+const toTop = () => {
+  window.scrollTo({ top: 0, behavior: "smooth" });
+};
+</script>
+
+<style lang="scss" scoped>
+@import "./detail.scss";
+</style>

+ 80 - 75
src/views/Events/index.scss

@@ -1,75 +1,80 @@
-.events-page {
-  &-panel {
-    padding: 20px 0;
-    background: var(--white-bg);
-
-    &__wrap {
-      margin: 0 auto;
-      width: 900px;
-
-      > ul {
-        display: flex;
-        flex-direction: column;
-        gap: 40px;
-        margin-top: 30px;
-      }
-    }
-    h1 {
-      font-size: 40px;
-      font-weight: bold;
-      font-family: Georgia;
-      line-height: 58px;
-      text-align: center;
-      background: url("./images/eve.gif") repeat-x 0 100%;
-      padding-bottom: 7px;
-    }
-  }
-  &-item {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    height: 450px;
-    background: url("https://en.capitalmuseum.org.cn/data/About/Events/1.jpg");
-    background-position: center;
-    background-size: cover;
-    box-shadow: 0 5px 5px rgba(0, 0, 0, 0.5);
-    cursor: pointer;
-
-    &__main {
-      width: 532px;
-      text-align: center;
-      color: white;
-    }
-    &__hd {
-      padding-bottom: 15px;
-      font-size: 24px;
-      font-family: Helvetica;
-      font-weight: bold;
-      line-height: 30px;
-      border-bottom: 4px solid hsla(0, 0%, 100%, 0.7);
-
-      p {
-        color: var(--van-primary-color);
-      }
-    }
-    &__container {
-      padding: 15px 0;
-      font-family: Georgia;
-      font-size: 16px;
-      color: hsla(0, 0%, 100%, 0.7);
-      line-height: 20px;
-
-      p {
-        margin: 0 auto;
-        width: 350px;
-        text-align: left;
-      }
-    }
-    i {
-      font-family: Georgia;
-      font-size: 16px;
-      line-height: 20px;
-      font-style: italic;
-    }
-  }
-}
+.events-page {
+  &-panel {
+    padding: 20px 0;
+    background: var(--white-bg);
+
+    &__wrap {
+      margin: 0 auto;
+      width: 900px;
+
+      > ul {
+        display: flex;
+        flex-direction: column;
+        gap: 40px;
+        margin-top: 30px;
+        min-height: 450px;
+      }
+    }
+    h1 {
+      font-size: 40px;
+      font-weight: bold;
+      font-family: Georgia;
+      line-height: 58px;
+      text-align: center;
+      background: url("./images/eve.gif") repeat-x 0 100%;
+      padding-bottom: 7px;
+    }
+  }
+  &-item {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 450px;
+    background-position: center;
+    background-size: cover;
+    box-shadow: 0 5px 5px rgba(0, 0, 0, 0.5);
+    cursor: pointer;
+
+    &__main {
+      width: 532px;
+      text-align: center;
+      color: white;
+    }
+    &__hd {
+      padding-bottom: 15px;
+      font-size: 24px;
+      font-family: Helvetica;
+      font-weight: bold;
+      line-height: 30px;
+      border-bottom: 4px solid hsla(0, 0%, 100%, 0.7);
+
+      p {
+        color: var(--van-primary-color);
+      }
+    }
+    &__container {
+      padding: 15px 0;
+      font-family: Georgia;
+      font-size: 16px;
+      color: hsla(0, 0%, 100%, 0.7);
+      line-height: 20px;
+
+      p {
+        margin: 0 auto;
+        width: 350px;
+        text-align: left;
+      }
+    }
+    i {
+      font-family: Georgia;
+      font-size: 16px;
+      line-height: 20px;
+      font-style: italic;
+    }
+  }
+  &__pagination {
+    margin-top: 20px;
+    display: flex;
+    justify-content: center;
+  }
+}

+ 109 - 59
src/views/Events/index.vue

@@ -1,59 +1,109 @@
-<template>
-  <div
-    class="events-page"
-    data-aria-viewport-area
-    tabindex="0"
-    aria-description="You've reached the banner area of the Events page; this area has one image; please use the tab key to go through the content."
-  >
-    <Breadcrumb :parents="[]" :cur-route="{ name: 'Events' }" />
-
-    <div
-      class="container events-page-panel"
-      data-aria-viewport-area
-      tabindex="0"
-      aria-description="You've reached the content area of the Events page, please use the tab key to navigate through the content."
-    >
-      <div class="events-page-panel__wrap">
-        <h1>EVENTS</h1>
-
-        <ul>
-          <li
-            class="events-page-item"
-            tabindex="0"
-            aria-label="Link"
-            @click="$router.push({ name: 'EventsDetail', params: { id: 2.1 } })"
-          >
-            <div class="events-page-item__main">
-              <div class="events-page-item__hd">
-                <h2>International Museum Day 2021</h2>
-                <p>International Symposium</p>
-              </div>
-
-              <div class="events-page-item__container">
-                <p>■ Introduction</p>
-                <p>
-                  ■ Directors' Roundtable of Development of Contemporary Museums
-                  and Exhibition Interpretation
-                </p>
-                <p>
-                  ■ Development of a Contemporary Museum and Exhibition
-                  Interpretation
-                </p>
-              </div>
-
-              <i class="events-page-item__date">Date:May 18, 2021</i>
-            </div>
-          </li>
-        </ul>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import Breadcrumb from "@/components/Breadcrumb/index.vue";
-</script>
-
-<style lang="scss" scoped>
-@import "./index.scss";
-</style>
+<template>
+  <div
+    class="events-page"
+    data-aria-viewport-area
+    tabindex="0"
+    aria-description="You've reached the banner area of the Events page; this area has one image; please use the tab key to go through the content."
+  >
+    <Breadcrumb :parents="[]" :cur-route="{ name: 'Events' }" />
+
+    <div
+      class="container events-page-panel"
+      data-aria-viewport-area
+      tabindex="0"
+      aria-description="You've reached the content area of the Events page, please use the tab key to navigate through the content."
+    >
+      <div class="events-page-panel__wrap">
+        <h1>EVENTS</h1>
+
+        <ul v-loading="loading">
+          <template v-if="list.length">
+            <li
+              v-for="item in list"
+              :key="item.id"
+              class="events-page-item"
+              tabindex="0"
+              aria-label="Link"
+              :style="{
+                backgroundImage: `url(${baseUrl}${item.thumb})`,
+              }"
+              @click="
+                $router.push({ name: 'EventsDetail', params: { id: item.id } })
+              "
+            >
+              <div class="events-page-item__main">
+                <div class="events-page-item__hd">
+                  <h2>{{ item.name }}</h2>
+                  <!-- <p>International Symposium</p> -->
+                </div>
+
+                <div class="events-page-item__container">
+                  <p v-for="title in item._rtfTitle" :key="title">
+                    ■ {{ title }}
+                  </p>
+                </div>
+
+                <i class="events-page-item__date">Date: {{ item.date }}</i>
+              </div>
+            </li>
+          </template>
+
+          <li v-if="noData" class="no-more">no more</li>
+        </ul>
+
+        <div v-if="total" class="events-page__pagination">
+          <Pagination
+            :total="total"
+            :page-size="DEFAULT_PAGE_SIZE"
+            @change="handlePage"
+          />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { getEventListApi, type EventListItem } from "@/api";
+import Breadcrumb from "@/components/Breadcrumb/index.vue";
+import Pagination from "@/components/Pagination/index.vue";
+import { getFormatDate } from "@/utils/date";
+import { usePagination, DEFAULT_PAGE_SIZE } from "@/utils/usePagination";
+import { getBaseURL } from "@dage/service";
+import { computed, onMounted } from "vue";
+
+const baseUrl = getBaseURL();
+const {
+  pageNum,
+  total,
+  loading,
+  list: _list,
+  noData,
+  getList,
+} = usePagination<EventListItem>((params) => {
+  return getEventListApi({
+    ...params,
+  });
+});
+
+const list = computed(() =>
+  _list.value.map((i) => ({
+    ...i,
+    _rtfTitle: JSON.parse(i.rtfTitle),
+    date: getFormatDate(i.dateStart, i.dateEnd),
+  }))
+);
+
+const handlePage = (page: number) => {
+  pageNum.value = page;
+  getList();
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

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

@@ -1,144 +1,154 @@
-<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>
+<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="item in showMore ? list : list.slice(0, 3)"
+        :key="item.id"
+        class="exh-detail-galleries-item"
+      >
+        <ElImage fit="cover" :src="baseUrl + item.filePath" />
+
+        <div class="exh-detail-galleries-item__mask">
+          <div>
+            <SvgIcon
+              name="download"
+              color="var(--white-text-color)"
+              style="width: 26px; height: 26px"
+            />
+            <a :href="item.filePath" download tabindex="0"> Download </a>
+          </div>
+          <div @click="handlePreview(item.filePath)">
+            <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
+      v-if="list.length > 3"
+      class="exh-detail-galleries__more"
+      @click="showMore = !showMore"
+    >
+      <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 type { FileItem } from "@/api";
+import { getBaseURL } from "@dage/service";
+import { ref } from "vue";
+
+defineProps<{
+  list: FileItem[];
+}>();
+
+const baseUrl = getBaseURL();
+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 = (path: string) => {
+  previewSrcList.value = [baseUrl + path];
+  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>

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

@@ -1,138 +1,153 @@
-<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>
+<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="item in showMore ? list : list.slice(0, 5)"
+        :key="item.id"
+        class="exh-detail-object-item"
+      >
+        <ElImage
+          :src="baseUrl + item.filePath"
+          fit="cover"
+          style="height: 100%"
+        />
+
+        <div class="exh-detail-object-item__mask">
+          <div>
+            <SvgIcon
+              name="download"
+              color="var(--white-text-color)"
+              style="width: 26px; height: 26px"
+            />
+            <a :href="item.filePath" download tabindex="0"> Download </a>
+          </div>
+          <div @click="handlePreview(item.filePath)">
+            <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
+      v-if="list.length > 5"
+      class="exh-detail-object__more"
+      @click="showMore = !showMore"
+    >
+      <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 type { FileItem } from "@/api";
+import { getBaseURL } from "@dage/service";
+import { ref } from "vue";
+
+defineProps<{
+  list: FileItem[];
+}>();
+
+const baseUrl = getBaseURL();
+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 = (path: string) => {
+  previewSrcList.value = [baseUrl + path];
+  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>

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

@@ -1,119 +1,205 @@
-<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>
+<template>
+  <div
+    ref="wrapRef"
+    class="exh-detail-overview"
+    :style="{ height: hasMore ? `${height}px` : 'auto' }"
+  >
+    <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">{{ detail?.name }}</p>
+      <div>
+        <div v-if="detail?.dateStart">
+          <img :src="DateIcon" style="width: 18px; height: 20px" />
+          <p style="padding: 0 15px">{{ date }}</p>
+        </div>
+        <div v-if="detail?.typeRemark">
+          <img :src="ClockIcon" style="width: 20px; height: 20px" />
+          <p style="padding: 0 15px">
+            {{ detail.typeRemark }}
+          </p>
+        </div>
+        <div>
+          <img :src="AddressIcon" style="width: 14px; height: 20px" />
+          <p style="padding-left: 15px">{{ detail?.address }}</p>
+        </div>
+      </div>
+    </div>
+    <div
+      ref="mainRef"
+      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" tabindex="0">
+        <span>Exhibition Overview</span>
+      </p>
+
+      <div
+        v-for="item in rtf"
+        :key="item.id"
+        class="exh-detail-overview__content"
+      >
+        <p tabindex="0">{{ item.name }}</p>
+        <div v-html="item.txt" tabindex="0" />
+      </div>
+    </div>
+
+    <div v-if="hasMore" class="exh-detail-overview__more">
+      <div
+        tabindex="0"
+        aria-label="Button"
+        @click="showMore"
+        @keydown.enter.passive="showMore"
+      >
+        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 { computed, nextTick, ref, watch } from "vue";
+import { type ExhibitionDetail } from "@/api";
+import { MONTHS } from "@/utils/date";
+
+const props = defineProps<{
+  detail: null | ExhibitionDetail;
+}>();
+
+const wrapRef = ref();
+const mainRef = ref();
+const hasMore = ref(true);
+const height = ref(600);
+const rtf = computed<{ id: number; txt: string; name: string }[]>(() =>
+  props.detail ? JSON.parse(props.detail.rtf).txtArr : []
+);
+const date = computed(() => {
+  if (!props.detail || !props.detail.dateStart) return;
+
+  const start = new Date(props.detail.dateStart);
+  const end = new Date(props.detail.dateEnd);
+
+  if (start.getFullYear() === end.getFullYear()) {
+    return `${MONTHS[start.getDay()]} ${start.getDate()} - ${
+      MONTHS[end.getDay()]
+    } ${end.getDate()}, ${end.getFullYear()}`;
+  }
+
+  return `${
+    MONTHS[start.getDay()]
+  } ${start.getDate()}, ${start.getFullYear()} - ${
+    MONTHS[end.getDay()]
+  } ${end.getDate()}, ${end.getFullYear()}`;
+});
+
+watch(
+  () => props.detail,
+  (v) => {
+    if (!v) return;
+
+    nextTick(() => {
+      if (mainRef.value.offsetHeight <= 600) {
+        hasMore.value = false;
+      }
+    });
+  }
+);
+
+const showMore = () => {
+  const _h = mainRef.value.offsetHeight;
+  if (height.value + 400 > _h) {
+    height.value = _h;
+    hasMore.value = false;
+  } else {
+    height.value += 400;
+  }
+};
+
+defineExpose({
+  wrapRef,
+});
+</script>
+
+<style lang="scss" scoped>
+.exh-detail {
+  &-overview {
+    position: relative;
+    margin-bottom: 40px;
+    overflow: hidden;
+    background: var(--white-bg);
+    border: 1px solid #e0e0e0;
+
+    &__content {
+      > p {
+        font-weight: bold;
+      }
+      :deep(p) {
+        margin-top: 24px;
+        font-size: 18px;
+        line-height: 26px;
+        color: #1f1d1d;
+      }
+    }
+    &-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 - 56
src/views/Exhibitions/Detail/index.scss

@@ -1,56 +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;
-  }
-}
+.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;
+  }
+}

+ 75 - 25
src/views/Exhibitions/Detail/index.vue

@@ -1,25 +1,20 @@
 <template>
-  <div class="exh-detail">
-    <img
+  <div v-loading="loading" class="exh-detail">
+    <ElImage
+      v-if="detail"
       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"
+      :aria-description="detail?.name"
+      :src="baseUrl + detail?.thumbPc"
     />
 
     <div class="exh-detail-breadcrumb">
       <Breadcrumb
-        :parents="[
-        {
-          label: 'Exhibitions',
-          routeParams: {name: 'Exhibitions', params: {type: 1}}
-        },
-        {
-          label: NAV_LIST[Number($route.query.k as string)].name,
-          routeParams: {name: 'Exhibitions', params: {type: $route.query.k as string}}
-        },
-      ]"
+        :parents="routeParams"
+        :cur-route="{
+          name: detail?.name || '',
+        }"
       />
     </div>
 
@@ -27,11 +22,14 @@
       <!-- 悬浮目录 -->
       <FloatDirectory :list="directory" />
 
-      <Overview />
+      <Overview :detail="detail" />
 
-      <Objects ref="objectsRef" />
+      <Objects ref="objectsRef" :list="detail ? detail.exhibitsFile : []" />
 
-      <Galleries ref="galleriesRef" />
+      <Galleries
+        ref="galleriesRef"
+        :list="detail ? detail.exhibitionFile : []"
+      />
     </div>
 
     <!-- 回到顶部 -->
@@ -47,41 +45,93 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, ref } from "vue";
+import { computed, onMounted, ref } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import { getExhibitionDetailApi, type ExhibitionDetail } from "@/api";
 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";
+import { getBaseURL } from "@dage/service";
 
 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,
+      offsetTop: () => containerRef.value?.offsetTop || 0,
     },
     {
       label: "Exhibition Objects",
-      offsetTop:
-        (objectsRef.value?.wrapRef?.offsetTop || 0) + containerOffsetTop,
+      offsetTop: () =>
+        (objectsRef.value?.wrapRef?.offsetTop || 0) +
+        (containerRef.value?.offsetTop || 0),
     },
     {
       label: "Exhibition Galleries",
-      offsetTop:
-        (galleriesRef.value?.wrapRef?.offsetTop || 0) + containerOffsetTop,
+      offsetTop: () =>
+        (galleriesRef.value?.wrapRef?.offsetTop || 0) +
+        (containerRef.value?.offsetTop || 0),
+    },
+  ];
+
+  return stack;
+});
+
+const baseUrl = getBaseURL();
+const route = useRoute();
+const router = useRouter();
+const loading = ref(false);
+const detail = ref<ExhibitionDetail | null>(null);
+
+const routeParams = computed(() => {
+  const stack = [
+    {
+      label: "Exhibitions",
+      routeParams: { name: "Exhibitions", params: { type: 1 } },
     },
   ];
 
+  if (route.query.k) {
+    const num = Number(route.query.k as string);
+    stack.push({
+      label: NAV_LIST[num - 1].name,
+      routeParams: {
+        name: "Exhibitions",
+        params: { type: num },
+      },
+    });
+  }
+
   return stack;
 });
 
+onMounted(() => {
+  getExhibitionDetail();
+});
+
+const getExhibitionDetail = async () => {
+  try {
+    loading.value = true;
+    const data = await getExhibitionDetailApi(route.query.id as string);
+    detail.value = data;
+  } catch (err) {
+    // @ts-ignore
+    if (err.response.code === 2001) {
+      setTimeout(() => {
+        router.replace({ name: "404" });
+      }, 3000);
+    }
+  } finally {
+    loading.value = false;
+  }
+};
+
 const backTop = () => {
   window.scrollTo({ top: 0, behavior: "smooth" });
 };

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

@@ -11,6 +11,7 @@
     "
   >
     <ElImage
+      fit="cover"
       :src="baseUrl + item.thumb"
       aria-label="Image link"
       :aria-description="item.name"
@@ -44,7 +45,7 @@ const baseUrl = getBaseURL();
   color: var(--black-text-color);
   cursor: pointer;
 
-  > img {
+  .el-image {
     width: 240px;
     height: 100%;
   }

+ 3 - 1
src/views/Exhibitions/index.vue

@@ -85,7 +85,9 @@
         :aria-description="`You've reached the content area of the ${curRoute.name} page. To browse the content, please use the tab key.`"
       >
         <ListItem v-if="mode === MODE.LIST" :list="list" />
-        <ImgItem v-else :list="list" />
+        <div v-else style="display: flex; flex-wrap: wrap">
+          <ImgItem :list="list" />
+        </div>
 
         <p v-if="noData" class="no-more">no more</p>
       </div>

+ 2 - 2
src/views/JoinSupport/Give/detail.vue

@@ -1,9 +1,9 @@
 <template>
   <div class="give-detail">
     <div class="give-detail__main">
-      <h1 tabindex="0">{{ detail?.h3 }}</h1>
+      <h1 tabindex="0">{{ detail?.name }}</h1>
 
-      <div class="give-detail__txt" v-html="detail?.txt" />
+      <div class="give-detail__txt" v-html="detail?.rtf" />
 
       <div class="give-detail-contact">
         <p tabindex="1">86 (10) 63370488</p>

+ 3 - 3
src/views/JoinSupport/Volunteer/detail.vue

@@ -1,10 +1,10 @@
 <template>
   <div class="volunteer-detail">
-    <h1 :aria-label="detail?.h3">
-      {{ detail?.h3 }}
+    <h1 :aria-label="detail?.name">
+      {{ detail?.name }}
     </h1>
 
-    <div v-html="detail?.txt" class="volunteer-detail__txt" tabindex="0" />
+    <div v-html="detail?.rtf" class="volunteer-detail__txt" tabindex="0" />
 
     <template v-if="detail?.card">
       <div

+ 20 - 19
src/views/JoinSupport/index.scss

@@ -1,19 +1,20 @@
-.join-support {
-  background: white;
-
-  .page-nav {
-    background-color: white;
-  }
-
-  &-banner {
-    margin-top: -60px;
-    display: block;
-    width: 100%;
-    height: 300px;
-    object-fit: cover;
-  }
-
-  .breadcrumb {
-    margin-top: 0;
-  }
-}
+.join-support {
+  background: white;
+  overflow: hidden;
+
+  .page-nav {
+    background-color: white;
+  }
+
+  &-banner {
+    margin-top: -60px;
+    display: block;
+    width: 100%;
+    height: 300px;
+    object-fit: cover;
+  }
+
+  .breadcrumb {
+    margin-top: 0;
+  }
+}

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

@@ -55,9 +55,6 @@
         color: #1f1d1d;
         font-size: 14px;
       }
-      :deep(.media-wrap) {
-        text-align: center;
-      }
     }
   }
 }

+ 6 - 4
src/views/LearnEngage/List/index.vue

@@ -1,8 +1,10 @@
 <template>
-  <div v-loading="loading" class="learn-engage-container">
-    <Item v-for="item in list" :key="item.id" :item="item" />
+  <div style="overflow: hidden">
+    <div v-loading="loading" class="learn-engage-container">
+      <Item v-for="item in list" :key="item.id" :item="item" />
 
-    <p v-if="noData" class="no-more">no more</p>
+      <p v-if="noData" class="no-more">no more</p>
+    </div>
   </div>
 
   <div
@@ -14,7 +16,7 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, watch } from "vue";
+import { watch } from "vue";
 import { getLearnPageListApi, type LearnPageItem } from "@/api";
 import Pagination from "@/components/Pagination/index.vue";
 import Item from "../components/Item.vue";

+ 139 - 129
src/views/Search/index.scss

@@ -1,129 +1,139 @@
-.search-page {
-  &-title {
-    font-size: 18px;
-    line-height: 30px;
-    text-indent: 10px;
-    border-left: 2px solid var(--van-primary-color);
-
-    span {
-      margin-left: 10px;
-      color: var(--van-primary-color);
-    }
-  }
-  &-search {
-    display: flex;
-    align-items: center;
-    margin: 20px auto;
-    padding: 20px;
-    height: 80px;
-    overflow: hidden;
-    background-color: var(--white-bg);
-    border: 1px solid #c8c8c8;
-
-    input {
-      flex: 1;
-      padding: 0 10px;
-      height: 100%;
-      font-size: 16px;
-      border: 1px solid #c8c8c8;
-      box-sizing: border-box;
-    }
-    &__btn {
-      flex-shrink: 0;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      width: 20%;
-      height: 100%;
-      font-size: 16px;
-      color: #fff;
-      background-color: #855b31;
-      border: 1px solid #855b31;
-      cursor: pointer;
-    }
-  }
-  &-tabbar {
-    display: flex;
-    align-items: center;
-    overflow: hidden;
-    padding: 0 20px;
-    height: 78px;
-    background-color: var(--white-bg);
-    border: 1px solid #c8c8c8;
-
-    &__more {
-      p:first-child {
-        display: flex;
-        align-items: center;
-        gap: 2px;
-      }
-    }
-    li {
-      position: relative;
-      text-align: center;
-      padding: 0 10px;
-      height: 34px;
-      white-space: nowrap;
-      color: var(--gray2-text-color);
-
-      &:last-child::after {
-        display: none;
-      }
-      &::after {
-        content: "";
-        position: absolute;
-        top: 0;
-        right: 0;
-        width: 1px;
-        height: 14px;
-        background: var(--gray-text-color);
-      }
-      &.active {
-        color: var(--black-text-color);
-
-        &::after {
-          color: var(--black-text-color);
-        }
-      }
-      p:first-child {
-        cursor: pointer;
-        font-weight: 700;
-        font-size: 14px;
-      }
-      p:last-child {
-        margin-top: 8px;
-        font-size: 12px;
-      }
-    }
-  }
-  &__pagination {
-    display: flex;
-    justify-content: center;
-    padding: 30px 0;
-  }
-  &-item {
-    display: flex;
-    gap: 20px;
-    margin-top: 20px;
-    padding: 20px;
-    background-color: var(--white-bg);
-    border: 1px solid #c8c8c8;
-    cursor: pointer;
-
-    .el-image {
-      flex-shrink: 0;
-      width: 150px;
-      height: 150px;
-    }
-    h3 {
-      font-weight: 700;
-      font-size: 14px;
-      line-height: 30px;
-    }
-    p {
-      font-size: 14px;
-      color: var(--gray2-text-color);
-      line-height: 24px;
-    }
-  }
-}
+.search-page {
+  &-title {
+    font-size: 18px;
+    line-height: 30px;
+    text-indent: 10px;
+    border-left: 2px solid var(--van-primary-color);
+
+    span {
+      margin-left: 10px;
+      color: var(--van-primary-color);
+    }
+  }
+  &-search {
+    display: flex;
+    align-items: center;
+    margin: 20px auto;
+    padding: 20px;
+    height: 80px;
+    overflow: hidden;
+    background-color: var(--white-bg);
+    border: 1px solid #c8c8c8;
+
+    input {
+      flex: 1;
+      padding: 0 10px;
+      height: 100%;
+      font-size: 16px;
+      border: 1px solid #c8c8c8;
+      box-sizing: border-box;
+    }
+    &__btn {
+      flex-shrink: 0;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 20%;
+      height: 100%;
+      font-size: 16px;
+      color: #fff;
+      background-color: #855b31;
+      border: 1px solid #855b31;
+      cursor: pointer;
+    }
+  }
+  &-tabbar {
+    display: flex;
+    align-items: center;
+    overflow: hidden;
+    padding: 0 20px;
+    height: 78px;
+    background-color: var(--white-bg);
+    border: 1px solid #c8c8c8;
+
+    &__more {
+      p:first-child {
+        display: flex;
+        align-items: center;
+        gap: 2px;
+      }
+    }
+    li {
+      position: relative;
+      text-align: center;
+      padding: 0 10px;
+      height: 34px;
+      white-space: nowrap;
+      color: var(--gray2-text-color);
+
+      &:last-child::after {
+        display: none;
+      }
+      &::after {
+        content: "";
+        position: absolute;
+        top: 0;
+        right: 0;
+        width: 1px;
+        height: 14px;
+        background: var(--gray-text-color);
+      }
+      &.active {
+        color: var(--black-text-color);
+
+        &::after {
+          color: var(--black-text-color);
+        }
+      }
+      p:first-child {
+        cursor: pointer;
+        font-weight: 700;
+        font-size: 14px;
+      }
+      p:last-child {
+        margin-top: 8px;
+        font-size: 12px;
+      }
+    }
+  }
+  &__pagination {
+    display: flex;
+    justify-content: center;
+    padding-bottom: 30px;
+  }
+  &-list {
+    padding-bottom: 30px;
+  }
+  &-item {
+    display: flex;
+    align-items: flex-start;
+    gap: 20px;
+    margin-top: 20px;
+    padding: 20px;
+    background-color: var(--white-bg);
+    border: 1px solid #c8c8c8;
+    cursor: pointer;
+
+    &.no-rtf {
+      align-items: center;
+
+      h3 {
+        font-size: 24px;
+      }
+    }
+    .el-image {
+      flex-shrink: 0;
+      width: 150px;
+    }
+    h3 {
+      font-weight: 700;
+      font-size: 14px;
+      line-height: 30px;
+    }
+    p {
+      font-size: 14px;
+      color: var(--gray2-text-color);
+      line-height: 24px;
+    }
+  }
+}

+ 361 - 102
src/views/Search/index.vue

@@ -1,102 +1,361 @@
-<template>
-  <div class="search-page container">
-    <Breadcrumb :parents="[]" :cur-route="{ name: 'Search' }" />
-
-    <div class="search-page-title">Search<span>378 results</span></div>
-
-    <div
-      class="search-page-search"
-      data-aria-interaction-area
-      tabindex="0"
-      aria-description="You've reached the Search interactive section, please use the tab key to go through the content."
-    >
-      <input />
-      <div class="search-page-search__btn">Search</div>
-    </div>
-
-    <ul
-      class="search-page-tabbar"
-      data-aria-viewport-area
-      tabindex="0"
-      aria-description="You've reached the content area of the Search results page, please use the tab key to navigate through the content."
-    >
-      <li
-        v-for="(item, idx) in TABBAR.slice(0, showMore ? 7 : TABBAR.length)"
-        :key="item.id"
-        tabindex="0"
-        aria-label="Link"
-        :aria-description="item.name"
-        :class="{ active: idx === activeTabbar }"
-      >
-        <p>{{ item.name }}</p>
-        <p>(33)</p>
-      </li>
-
-      <li
-        v-if="showMore"
-        class="search-page-tabbar__more"
-        tabindex="0"
-        aria-label="Button"
-        aria-description="More"
-      >
-        <p @click="showMore = false">
-          More<SvgIcon
-            name="more"
-            color="var(--gray2-text-color)"
-            style="width: 10px; height: 10px"
-          />
-        </p>
-        <p />
-      </li>
-    </ul>
-
-    <ul class="search-page-list">
-      <li class="search-page-item">
-        <ElImage
-          src="https://en.capitalmuseum.org.cn/data/Exhibitions/Current/2.jpg"
-        />
-        <div class="search-page-item__inner">
-          <h3>Splendid Central Axis of Beijing</h3>
-          <p>
-            Starting from the planning and construction of the Central Axis of
-            the Capital Dadu of the Yuan Dynasty and with the ongoing
-            inheritance and carrying forward of past achievements over the later
-            dynasties, the Central Axis of Beijing has finally been made such a
-            magnificent presence as it stands now, with originality and
-            creativeness to be found everywhere along the Axis.
-          </p>
-        </div>
-      </li>
-    </ul>
-
-    <div class="search-page__pagination">
-      <Pagination :total="100" />
-    </div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import Breadcrumb from "@/components/Breadcrumb/index.vue";
-import Pagination from "@/components/Pagination/index.vue";
-import { ref } from "vue";
-
-const TABBAR = [
-  { id: 1, name: "All Results", cut: "All" },
-  { id: 2, name: "Visit", cut: "Visit" },
-  { id: 3, name: "Exhibitions", cut: "Exhibitions" },
-  { id: 4, name: "Collections", cut: "Collections" },
-  { id: 5, name: "Learn & Engage", cut: "Learn" },
-  { id: 6, name: "Research & Publications", cut: "Research" },
-  { id: 7, name: "Join & Support", cut: "Join" },
-  { id: 8, name: "About", cut: "About" },
-  { id: 9, name: "Events", cut: "Events" },
-  { id: 10, name: "Terms of Use", cut: "Terms" },
-  { id: 11, name: "Employment", cut: "Employment" },
-];
-const showMore = ref(true);
-const activeTabbar = ref(0);
-</script>
-
-<style lang="scss" scoped>
-@import "./index.scss";
-</style>
+<template>
+  <div class="search-page container">
+    <Breadcrumb :parents="[]" :cur-route="{ name: 'Search' }" />
+
+    <div class="search-page-title">
+      Search<span>{{ resultTotal }} results</span>
+    </div>
+
+    <div
+      class="search-page-search"
+      data-aria-interaction-area
+      tabindex="0"
+      aria-description="You've reached the Search interactive section, please use the tab key to go through the content."
+    >
+      <input v-model="keyword" @keyup.enter="handleSearch" />
+      <div class="search-page-search__btn" @click="handleSearch">Search</div>
+    </div>
+
+    <ul
+      class="search-page-tabbar"
+      data-aria-viewport-area
+      tabindex="0"
+      aria-description="You've reached the content area of the Search results page, please use the tab key to navigate through the content."
+    >
+      <li
+        tabindex="0"
+        aria-label="Link"
+        aria-description="All Results"
+        :class="{ active: activeTabbar === -1 }"
+        @click="activeTabbar = -1"
+      >
+        <p>All Results</p>
+        <p>({{ resultTotal }})</p>
+      </li>
+      <li
+        v-for="(item, idx) in tabbar.slice(0, showMore ? 7 : tabbar.length)"
+        :key="item.module"
+        tabindex="0"
+        aria-label="Link"
+        :aria-description="item.name"
+        :class="{ active: idx === activeTabbar }"
+        @click="activeTabbar = idx"
+      >
+        <p>{{ item.name }}</p>
+        <p>({{ item.total }})</p>
+      </li>
+
+      <li
+        v-if="showMore"
+        class="search-page-tabbar__more"
+        tabindex="0"
+        aria-label="Button"
+        aria-description="More"
+      >
+        <p @click="showMore = false">
+          More<SvgIcon
+            name="more"
+            color="var(--gray2-text-color)"
+            style="width: 10px; height: 10px"
+          />
+        </p>
+      </li>
+    </ul>
+
+    <ul class="search-page-list">
+      <li
+        v-for="item in list"
+        :key="item.name"
+        class="search-page-item"
+        :class="{
+          'no-rtf': !item.rtf,
+        }"
+        @click="handleClick(item)"
+      >
+        <ElImage v-if="item.thumb" :src="item.thumb" />
+        <div class="search-page-item__inner">
+          <h3 class="limit-line" :class="{ 'line-2': !item.rtf }">
+            {{ item.name }}
+          </h3>
+          <p>
+            {{ item.rtf }}
+          </p>
+        </div>
+      </li>
+
+      <li
+        v-if="!loading && !list.length"
+        style="position: relative; height: 300px"
+      >
+        <span class="no-more">no information...</span>
+      </li>
+    </ul>
+
+    <div v-if="list.length >= PAGE_SIZE" class="search-page__pagination">
+      <Pagination
+        :total="activeTabbar === -1 ? resultTotal : tabbar[activeTabbar].total"
+        @change="handlePage"
+      />
+    </div>
+  </div>
+
+  <CollectionDetailDialog
+    v-model:visible="collectionVisible"
+    :id="checkedItemId"
+  />
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, watch } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import { cloneDeep, isUndefined } from "lodash-unified";
+import { getBaseURL } from "@dage/service";
+import { JoinSupport, About, TermsOfUse, Employment, Visit } from "@/data";
+import { searchApi, type SearchItem } from "@/api";
+import Breadcrumb from "@/components/Breadcrumb/index.vue";
+import Pagination from "@/components/Pagination/index.vue";
+import CollectionDetailDialog from "@/views/Collections/components/DetailDialog/index.vue";
+
+const _VISIT = cloneDeep(Visit);
+_VISIT.Reservation.card.forEach((item) => {
+  _VISIT.Reservation.rtf += item.pp;
+});
+
+const PAGE_SIZE = 20;
+const FRONT_DATA = [
+  ...Object.values(JoinSupport).flat(),
+  ...Object.values(_VISIT).flat(),
+  About.Director,
+  TermsOfUse,
+  Employment,
+];
+
+const baseUrl = getBaseURL();
+const route = useRoute();
+const router = useRouter();
+const tabbar = ref([
+  { name: "Visit", module: "visit", total: 0 },
+  { name: "Exhibitions", module: "exhibition", total: 0 },
+  { name: "Collections", module: "collection", total: 0 },
+  { name: "Learn & Engage", module: "learn", total: 0 },
+  {
+    name: "Research & Publications",
+    module: "publish",
+    total: 0,
+  },
+  { name: "Join & Support", module: "join", total: 0 },
+  { name: "About", module: "about", total: 0 },
+  { name: "Events", module: "event", total: 0 },
+  { name: "Terms of Use", module: "terms", total: 0 },
+  { name: "Employment", module: "employment", total: 0 },
+]);
+
+const resultTotal = computed(() =>
+  tabbar.value.reduce((t, item) => (t += item.total), 0)
+);
+const activeTabbar = ref(
+  isUndefined(route.query.activeTabbar) ? -1 : Number(route.query.activeTabbar)
+);
+const showMore = ref(activeTabbar.value < 7);
+const keyword = ref("");
+const list = ref<SearchItem[]>([]);
+
+const collectionVisible = ref(false);
+const checkedItemId = ref<number>(0);
+
+/**
+ * 处理富文本标签,返回限制后的文字
+ */
+const getAbstract = (str: string) => {
+  const txt = str.replace(/<[^>]*>/g, " ");
+  return `${txt.slice(0, 500)}${txt.length > 500 ? "..." : ""}`;
+};
+
+const loading = ref(false);
+const pageNum = ref(1);
+const getList = async () => {
+  try {
+    loading.value = true;
+    const cloneTabbar = [...tabbar.value].map((i) => ({ ...i, total: 0 }));
+    const k = decodeURIComponent(route.query.keyword as string);
+    const reg = new RegExp(k, "i");
+    let stack: any[] = [];
+
+    stack = FRONT_DATA.filter((i) => {
+      // 判断是否满足查询条件
+      const res = reg.test(i.rtf);
+      const res2 = reg.test(i.name);
+      if (!res && !res2) return false;
+
+      switch (i.module) {
+        case "visit":
+          cloneTabbar[0].total += 1;
+          break;
+        case "join":
+          cloneTabbar[5].total += 1;
+          break;
+        case "about":
+          cloneTabbar[6].total += 1;
+          break;
+        case "terms":
+          cloneTabbar[8].total += 1;
+          break;
+        case "employment":
+          cloneTabbar[9].total += 1;
+          break;
+      }
+
+      // 如果非查询全部,先判断类型是否一致
+      if (
+        activeTabbar.value !== -1 &&
+        tabbar.value[activeTabbar.value]?.module !== i.module
+      )
+        return false;
+
+      return true;
+    }).map((i) => ({
+      ...i,
+      rtf: getAbstract(i.rtf),
+    }));
+
+    const BACKEND_MODULE_INDEX = [-1, 1, 2, 3, 4, 7];
+    const data = await searchApi({
+      pageNum: pageNum.value,
+      pageSize: PAGE_SIZE,
+      searchKey: k,
+      // @ts-ignore
+      module:
+        activeTabbar.value === -1 ||
+        !BACKEND_MODULE_INDEX.includes(activeTabbar.value)
+          ? undefined
+          : tabbar.value[activeTabbar.value].module,
+    });
+
+    Object.keys(data.count).forEach((key) => {
+      const target = cloneTabbar.find((i) => i.module === key);
+      if (target) target.total = data.count[key];
+    });
+
+    if (BACKEND_MODULE_INDEX.includes(activeTabbar.value)) {
+      stack.unshift(
+        ...data.list.records.map((i: SearchItem) => ({
+          ...i,
+          thumb: baseUrl + i.thumb,
+          rtf: i.rtf
+            ? getAbstract(
+                JSON.parse(i.rtf).txtArr.reduce(
+                  (arr: string, item: Record<string, string>) =>
+                    (arr += item.txt),
+                  ""
+                )
+              )
+            : "",
+        }))
+      );
+    }
+
+    keyword.value = k;
+    tabbar.value = cloneTabbar;
+    list.value = stack;
+  } finally {
+    loading.value = false;
+  }
+};
+
+watch(
+  route,
+  () => {
+    getList();
+  },
+  {
+    immediate: true,
+  }
+);
+
+watch(activeTabbar, () => {
+  router.replace({
+    name: "Search",
+    query: { ...route.query, activeTabbar: activeTabbar.value },
+  });
+});
+
+const handleSearch = () => {
+  router.replace({
+    name: "Search",
+    query: { keyword: encodeURIComponent(keyword.value) },
+  });
+};
+
+const handlePage = (v: number) => {
+  pageNum.value = v;
+  getList();
+};
+
+const VISIT_MAP: Record<string, Function> = {
+  reservation: () => {
+    router.push({ name: "Reservation" });
+  },
+  guide: () => {
+    router.push({ name: "Guide" });
+  },
+  accessibility: () => {
+    router.push({ name: "Accessibility" });
+  },
+  shop: () => {
+    router.push({ name: "Shop" });
+  },
+};
+const handleClick = (item: SearchItem) => {
+  switch (item.module as string) {
+    case "visit":
+      VISIT_MAP[item.type]();
+      break;
+    case "learn":
+      router.push({ name: "LearnEngageDetail", query: { id: item.id } });
+      break;
+    case "exhibition":
+      router.push({ name: "ExhibitionsDetail", query: { id: item.id } });
+      break;
+    case "event":
+      router.push({ name: "EventsDetail", params: { id: item.id } });
+      break;
+    case "collection":
+      checkedItemId.value = item.id;
+      collectionVisible.value = true;
+      break;
+    case "publish":
+      if (item.type === "Magazines") {
+        router.push({
+          name: "PublicationsDetail",
+          query: { id: item.id, type: 0, title: encodeURIComponent(item.name) },
+        });
+      } else {
+        // 展览目录直接打开pdf
+        window.open(baseUrl + item.filePath);
+      }
+      break;
+    case "join":
+      if (item.type === "volunteer") {
+        router.push({ name: "VolunteerDetail", query: { id: item.id } });
+      } else {
+        router.push({ name: "GiveDetail", query: { id: item.id } });
+      }
+      break;
+    case "about":
+      if (item.type === "director") {
+        router.push({ name: "AboutDirector" });
+      }
+      break;
+    case "terms":
+      router.push({ name: "Use" });
+      break;
+    case "employment":
+      router.push({ name: "Employment" });
+      break;
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 74 - 74
src/views/Use/index.scss

@@ -1,74 +1,74 @@
-.use-page {
-  &-panel {
-    background: var(--white-bg);
-    border: 1px solid #e0e0e0;
-
-    &-hd {
-      padding: 40px 40px 20px;
-      border-bottom: 1px solid #e0e0e0;
-
-      h3 {
-        margin-bottom: 10px;
-        font-size: 22px;
-        line-height: 44px;
-        font-weight: bold;
-      }
-      &__ft {
-        display: flex;
-        align-items: center;
-        gap: 10px;
-        font-size: 14px;
-        color: var(--gray-text-color);
-
-        .date {
-          margin-right: 20px;
-        }
-      }
-    }
-    &-main {
-      padding: 30px 40px;
-
-      p {
-        margin-top: 24px;
-        font-size: 18px;
-        line-height: 26px;
-      }
-    }
-    &__title {
-      position: relative;
-      text-align: center;
-      font-size: 24px;
-      font-weight: bold;
-      color: var(--van-primary-color);
-
-      &::after {
-        content: "";
-        position: absolute;
-        top: 50%;
-        left: 50%;
-        width: 320px;
-        height: 1px;
-        border-top: 1px solid var(--gray2-text-color);
-        transform: translate(-50%, -50%);
-      }
-      span {
-        position: relative;
-        padding: 0 20px;
-        background: var(--white-bg);
-        z-index: 1;
-      }
-    }
-  }
-  &__back {
-    display: block;
-    margin: 30px auto;
-    width: 160px;
-    height: 38px;
-    font-size: 16px;
-    background: #f1f1f1;
-    font-weight: bold;
-    color: var(--black-text-color);
-    border: 1px solid var(--black-text-color);
-    cursor: pointer;
-  }
-}
+.use-page {
+  &-panel {
+    background: var(--white-bg);
+    border: 1px solid #e0e0e0;
+
+    &-hd {
+      padding: 40px 40px 20px;
+      border-bottom: 1px solid #e0e0e0;
+
+      h3 {
+        margin-bottom: 10px;
+        font-size: 22px;
+        line-height: 44px;
+        font-weight: bold;
+      }
+      &__ft {
+        display: flex;
+        align-items: center;
+        gap: 10px;
+        font-size: 14px;
+        color: var(--gray-text-color);
+
+        .date {
+          margin-right: 20px;
+        }
+      }
+    }
+    &-main {
+      padding: 30px 40px;
+
+      :deep(p) {
+        margin-top: 24px;
+        font-size: 18px;
+        line-height: 26px;
+      }
+    }
+    &__title {
+      position: relative;
+      text-align: center;
+      font-size: 24px;
+      font-weight: bold;
+      color: var(--van-primary-color);
+
+      &::after {
+        content: "";
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        width: 320px;
+        height: 1px;
+        border-top: 1px solid var(--gray2-text-color);
+        transform: translate(-50%, -50%);
+      }
+      span {
+        position: relative;
+        padding: 0 20px;
+        background: var(--white-bg);
+        z-index: 1;
+      }
+    }
+  }
+  &__back {
+    display: block;
+    margin: 30px auto;
+    width: 160px;
+    height: 38px;
+    font-size: 16px;
+    background: #f1f1f1;
+    font-weight: bold;
+    color: var(--black-text-color);
+    border: 1px solid var(--black-text-color);
+    cursor: pointer;
+  }
+}

+ 42 - 118
src/views/Use/index.vue

@@ -1,118 +1,42 @@
-<template>
-  <div class="use-page">
-    <Breadcrumb :parents="[]" :cur-route="{ name: 'Terms of Use' }" />
-
-    <div class="container use-page-panel">
-      <div class="use-page-panel-hd">
-        <h3>Terms of Use</h3>
-
-        <div class="use-page-panel-hd__ft">
-          <img src="@/assets/images/bg_5.png" />
-          <div class="date">2018</div>
-
-          <img src="@/assets/images/bg_7.png" />
-          <div class="address">Capital Museum</div>
-        </div>
-      </div>
-
-      <div class="use-page-panel-main">
-        <div class="use-page-panel__title">
-          <span>Exhibition Overview</span>
-        </div>
-
-        <p tabindex="0">Legal Notice</p>
-        <p tabindex="0">
-          Welcome to www.capitalmuseum.org.cn (hereinafter referred to as "this
-          website"). You are expected to comply with following terms while using
-          this website:
-        </p>
-        <p tabindex="0">
-          Copyright of all contents on this website, including but not limited
-          to the website design, texts, pictures, audio and video works, etc.,
-          belong to the Capital Museum and all the parties concerned. All media,
-          websites, organizations or individuals are allowed to read these
-          contents, but cannot use them in the ways including but not limited to
-          reprinting, excerpting, linking, reposting, publication, transfer,
-          distribution, etc. And unauthorized use of this website and its
-          contents for commercial purposes is also prohibited.
-        </p>
-        <p tabindex="0">
-          If you download materials you consider you need from this website for
-          non-commercial use (except for information with copyrights and
-          proprietary rights), please contact us for permission.
-        </p>
-        <p tabindex="0">
-          Any reprints and quotes of any copyrighted articles on this website
-          should conform to following requirements:
-        </p>
-        <p
-          tabindex="0"
-          :aria-description="`(1) For non-commercial, non-profit and non-advertising uses, author's name and source of the used article or picture, &quot;Capital Museum Website&quot; or &quot;${$homePageUrl}&quot;, should be attached.`"
-        >
-          (1) For non-commercial, non-profit and non-advertising uses, author's
-          name and source of the used article or picture, "首都博物馆网站" or
-          "{{ $homePageUrl }}", should be attached.
-        </p>
-        <p
-          tabindex="0"
-          :aria-description="`(2) For business, profit-making and advertising use, you should obtain consent of the original author, and attach the name of that author, limits of authority and the source, &quot;Capital Museum Website&quot;or &quot;${$homePageUrl}&quot;.`"
-        >
-          (2) For business, profit-making and advertising use, you should obtain
-          consent of the original author, and attach the name of that author,
-          limits of authority and the source, "首都博物馆网站"or "{{
-            $homePageUrl
-          }}".
-        </p>
-        <p tabindex="0">
-          (3) Any modification and cancellation of any articles or pictures
-          should be approved by the author, with the limits of authority
-          attached.
-        </p>
-        <p tabindex="0">
-          This website and the Capital Museum are not liable for any direct or
-          indirect incidental damage resulting from the use or inability to use
-          the information on this website. This website also does not assume any
-          criminal or civil liability arising from any violation of the
-          provisions of this website or the laws of the People's Republic of
-          China.
-        </p>
-        <p tabindex="0">
-          This website will make announcement in advance if services are to be
-          suspended due to system maintenance or upgrading. The website and the
-          Capital Museum are not be liable for any inconvenience or losses
-          caused by the suspension of services due to hardware failure or force
-          majeure.
-        </p>
-        <p tabindex="0">
-          The right of final interpretation of all contents of this website is
-          owned by the Capital Museum.
-        </p>
-        <p tabindex="0">
-          To safeguard rights and interests of this website and respect for the
-          authors' copyrights, we entrust Lyu Xiaojing of the Beijing Realizer
-          Law Firm as the legal adviser to this website. Anyone violating the
-          terms of this notice and laws will be held accountable. If any
-          contents published and reposted on this website violate your
-          copyrights, please contact our lawyer within two weeks.
-        </p>
-        <p tabindex="0">The Capital Museum</p>
-      </div>
-    </div>
-
-    <button class="use-page__back" @click="backTop">Back to top</button>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import Breadcrumb from "@/components/Breadcrumb/index.vue";
-
-const $homePageUrl = location.origin;
-
-const backTop = () => {
-  window.scrollTo({ top: 0, behavior: "smooth" });
-};
-</script>
-
-<style lang="scss" scoped>
-@import "./index.scss";
-</style>
+<template>
+  <div class="use-page">
+    <Breadcrumb :parents="[]" :cur-route="{ name: TermsOfUse.name }" />
+
+    <div class="container use-page-panel">
+      <div class="use-page-panel-hd">
+        <h3>{{ TermsOfUse.name }}</h3>
+
+        <div class="use-page-panel-hd__ft">
+          <img src="@/assets/images/bg_5.png" />
+          <div class="date">2018</div>
+
+          <img src="@/assets/images/bg_7.png" />
+          <div class="address">Capital Museum</div>
+        </div>
+      </div>
+
+      <div class="use-page-panel-main">
+        <div class="use-page-panel__title">
+          <span>Exhibition Overview</span>
+        </div>
+
+        <div v-html="TermsOfUse.rtf" />
+      </div>
+    </div>
+
+    <button class="use-page__back" @click="backTop">Back to top</button>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import Breadcrumb from "@/components/Breadcrumb/index.vue";
+import { TermsOfUse } from "@/data";
+
+const backTop = () => {
+  window.scrollTo({ top: 0, behavior: "smooth" });
+};
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 32 - 32
src/views/Visit/Accessibility/index.scss

@@ -1,32 +1,32 @@
-.Visit5 {
-  .conten {
-    color: #000;
-    width: 1200px;
-    font-size: 18px;
-    line-height: 26px;
-    margin: auto;
-    .row {
-      width: 866px;
-      display: flex;
-      & > div {
-        width: 50%;
-        & > h3 {
-          padding-top: 30px;
-
-          font-size: 18px;
-          font-weight: 700;
-          margin-bottom: 20px;
-        }
-        & > p {
-          font-size: 14px;
-        }
-        & > img {
-          width: 100%;
-        }
-      }
-      .blank {
-        padding-left: 10px;
-      }
-    }
-  }
-}
+.Visit5 {
+  .conten {
+    color: #000;
+    width: 1200px;
+    font-size: 18px;
+    line-height: 26px;
+    margin: auto;
+    :deep(.row) {
+      width: 866px;
+      display: flex;
+      & > div {
+        width: 50%;
+        & > h3 {
+          padding-top: 30px;
+
+          font-size: 18px;
+          font-weight: 700;
+          margin-bottom: 20px;
+        }
+        & > p {
+          font-size: 14px;
+        }
+        & > img {
+          width: 100%;
+        }
+      }
+      .blank {
+        padding-left: 10px;
+      }
+    }
+  }
+}

+ 17 - 72
src/views/Visit/Accessibility/index.vue

@@ -1,72 +1,17 @@
-<template>
-  <div
-    class="Visit5"
-    data-aria-viewport-area
-    aria-description="You have reached the content area under the Accessibility page. To browse the content, please use the tab key."
-  >
-    <div class="conten">
-      <div class="row">
-        <div>
-          <h3>Facilities for the Handicapped</h3>
-          <p>
-            Wheelchairs are available for visitors with special needs.
-            Professional consultants and guides are on hand to help.
-          </p>
-          <br />
-          <p>
-            In addition to professional commentators in the exhibition halls, 15
-            advanced self-service screens and six special ones for people in
-            wheelchairs have been installed in the exhibit areas. Visitors can
-            read and download data, or carry out interactive operations via
-            these screens. A total of 18 card-operated telephones, including six
-            for those in wheelchairs, have also been installed.
-          </p>
-        </div>
-        <div>
-          <img
-            src="@/assets/images/Visit/access1.jpg"
-            alt="Facilities for the Handicapped"
-            aria-description="Facilities for the Handicapped"
-          />
-        </div>
-      </div>
-      <div class="row">
-        <div>
-          <img
-            src="@/assets/images/Visit/access2.jpg"
-            alt="Nursery Room"
-            aria-description="Nursery Room"
-          />
-        </div>
-        <div class="blank">
-          <h3>Nursery Room</h3>
-          <p>
-            Nursery room is available on the 5th floor of the museum.
-            Professional consultants and guides are on hand to help.
-          </p>
-        </div>
-      </div>
-      <div class="row">
-        <div>
-          <h3>Lockers</h3>
-          <p>
-            Lockers are available on the 1st floor, the right-hand side of the
-            north entrance. Professional consultants and guides are on hand to
-            help.
-          </p>
-        </div>
-        <div>
-          <img
-            src="@/assets/images/Visit/access3.jpg"
-            alt="Lockers"
-            aria-description="Lockers"
-          />
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-@import "./index.scss";
-</style>
+<template>
+  <div
+    class="Visit5"
+    data-aria-viewport-area
+    aria-description="You have reached the content area under the Accessibility page. To browse the content, please use the tab key."
+  >
+    <div class="conten" v-html="Visit.Accessibility.rtf" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { Visit } from "@/data";
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 6 - 6
src/views/Visit/Calendar/components/Calendar.vue

@@ -13,15 +13,11 @@
       :date="date"
       :selected-day="realSelectedDay"
       :first-day-of-week="1"
+      :disabled="disabled"
       @pick="pickDay"
     >
       <template #dateCell="{ data }">
-        {{ data.data.day.split("-")[2] }}
-        <div class="dots">
-          <div class="dot" />
-          <div class="dot events" />
-          <div class="dot learn" />
-        </div>
+        <slot :data="data" />
       </template>
     </DateTable>
 
@@ -44,6 +40,7 @@ export default {
   },
   props: {
     modelValue: [Date, String, Number],
+    disabled: Boolean,
   },
   provide() {
     return {
@@ -111,6 +108,8 @@ export default {
       return val instanceof Date ? val : new Date(val);
     },
     selectDate(type) {
+      if (this.disabled) return;
+
       let day = "";
       if (type === "prev-month") {
         day = `${this.prevMonthDatePrefix}-01`;
@@ -120,6 +119,7 @@ export default {
 
       if (day === this.formatedDate) return;
       this.pickDay(day);
+      this.$emit("changeMonth");
     },
   },
 };

+ 6 - 1
src/views/Visit/Calendar/components/DateTable/index.vue

@@ -1,6 +1,6 @@
 <template>
   <table
-    :class="['el-calendar-table', { 'is-range': isInRange }]"
+    :class="['el-calendar-table', { 'is-range': isInRange, disabled }]"
     cellspacing="0"
     cellpadding="0"
   >
@@ -50,6 +50,7 @@ import { ref, computed, inject, toRefs, defineComponent } from "vue";
 export default defineComponent({
   name: "CalendarComponent",
   props: {
+    disabled: Boolean,
     selectedDay: String,
     range: {
       type: Array,
@@ -183,6 +184,7 @@ export default defineComponent({
     }
 
     function pickDay({ text, type }) {
+      if (this.disabled) return;
       const date = getFormateDate(text, type);
       emit("pick", date);
     }
@@ -247,5 +249,8 @@ export default defineComponent({
     height: 85px;
     cursor: pointer;
   }
+  &.disabled .el-calendar-day {
+    cursor: not-allowed;
+  }
 }
 </style>

+ 216 - 214
src/views/Visit/Calendar/index.scss

@@ -1,214 +1,216 @@
-.calendar {
-  margin-bottom: 30px;
-}
-
-.dots {
-  display: flex;
-  gap: 8px;
-  margin-top: 10px;
-
-  .dot {
-    width: 5px;
-    height: 5px;
-    border-radius: 5px;
-    background-color: #a9da8c;
-
-    &.events {
-      background-color: #95d2ff;
-    }
-    &.learn {
-      background-color: #ffce7e;
-    }
-  }
-}
-
-.panel {
-  display: flex;
-  gap: 19px;
-  color: #202020;
-
-  > div {
-    background: var(--white-bg);
-    box-shadow: 0px 10px 32px 0px rgba(146, 146, 146, 0.25);
-    border-radius: 10px;
-    overflow: hidden;
-  }
-  &-lf {
-    flex: 1;
-    display: flex;
-    flex-direction: column;
-
-    &-swipe {
-      height: 440px;
-
-      &-indicator {
-        position: absolute;
-        right: 0;
-        top: 50%;
-        transform: translateY(-50%);
-        z-index: 1;
-
-        li {
-          position: relative;
-          padding: 8px 15px;
-          cursor: pointer;
-
-          &.is-active {
-            &::before {
-              background-color: var(--van-primary-color);
-            }
-            &::after {
-              content: "";
-              position: absolute;
-              top: 50%;
-              left: 50%;
-              width: 17px;
-              height: 17px;
-              border-radius: 17px;
-              border: 2px solid var(--van-primary-color);
-              transform: translate(-50%, -50%);
-            }
-          }
-          &::before {
-            content: "";
-            position: relative;
-            display: block;
-            width: 10px;
-            height: 10px;
-            opacity: 0.5;
-            border-radius: 10px;
-            background-color: #918784;
-            z-index: 1;
-          }
-        }
-      }
-    }
-    &__footer {
-      flex: 1;
-      display: flex;
-      flex-direction: column;
-      justify-content: center;
-      padding: 0 22px;
-      font-size: 19px;
-      color: var(--black-text-color);
-
-      div:first-child {
-        display: flex;
-        align-items: center;
-        font-weight: bold;
-
-        p {
-          flex: 1;
-          width: 0;
-          line-height: 24px;
-        }
-        span {
-          margin-left: 16px;
-          padding: 5px;
-          font-size: 12px;
-          color: #d2b986;
-          background: rgba(210, 185, 134, 0.3);
-          border-radius: 4px;
-          white-space: nowrap;
-          border: 1px solid #d2b986;
-        }
-      }
-      p:last-child {
-        margin-top: 4px;
-      }
-    }
-  }
-}
-.main {
-  margin-top: 20px;
-  padding: 22px 21px;
-  color: var(--black-text-color);
-  background: var(--white-bg);
-  border-radius: 10px;
-  box-shadow: 0px 8px 25px 0px rgba(146, 146, 146, 0.25);
-
-  :deep(.el-date-editor) {
-    width: 233px;
-    height: 45px;
-
-    .el-input__inner {
-      height: 45px;
-      line-height: 45px;
-      border-radius: 4px;
-    }
-  }
-  .el-button {
-    padding: 0 35px;
-    height: 45px;
-  }
-  &__header {
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    padding: 0 22px 24px;
-    border-bottom: 1px dashed rgba(32, 32, 32, 0.5);
-  }
-  &__list {
-    .visit8-card {
-      display: flex;
-      padding: 28px 23px;
-      height: 320px;
-      box-sizing: border-box;
-      border-bottom: 1px dashed rgba(32, 32, 32, 0.5);
-      cursor: pointer;
-
-      &__left {
-        flex: 0 0 185px;
-        display: flex;
-        flex-direction: column;
-        justify-content: space-between;
-        font-size: 37px;
-        line-height: 48px;
-
-        img {
-          width: 50px;
-          height: 50px;
-        }
-      }
-      &__center {
-        flex: 1;
-        display: flex;
-        flex-direction: column;
-        justify-content: space-between;
-        padding: 0 26px 0 69px;
-
-        > p {
-          font-size: 37px;
-          font-weight: bold;
-          line-height: 48px;
-        }
-        &__inner {
-          line-height: 18px;
-        }
-      }
-      &__right {
-        flex: 0 0 420px;
-        height: 268px;
-      }
-      &__tag {
-        display: inline-block;
-        padding: 8px 15px;
-        color: #d2b986;
-        background: rgba(210, 185, 134, 0.3);
-        border-radius: 4px;
-        border: 1px solid #d2b986;
-      }
-    }
-  }
-}
-.page {
-  display: flex;
-  justify-content: center;
-  padding-top: 30px;
-}
-.empty {
-  text-align: center;
-  font-size: 20px;
-  padding-top: 30px;
-  color: #666666;
-}
+.calendar {
+  margin-bottom: 30px;
+}
+
+.dots {
+  display: flex;
+  gap: 8px;
+  margin-top: 10px;
+
+  .dot {
+    width: 5px;
+    height: 5px;
+    border-radius: 5px;
+    background-color: #a9da8c;
+
+    &.events {
+      background-color: #95d2ff;
+    }
+    &.learn {
+      background-color: #ffce7e;
+    }
+  }
+}
+
+.panel {
+  display: flex;
+  gap: 19px;
+  color: #202020;
+
+  > div {
+    background: var(--white-bg);
+    box-shadow: 0px 10px 32px 0px rgba(146, 146, 146, 0.25);
+    border-radius: 10px;
+    overflow: hidden;
+  }
+  &-lf {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+
+    &-swipe {
+      height: 440px;
+
+      .el-image {
+        width: 100%;
+        height: 100%;
+        cursor: pointer;
+      }
+      &-indicator {
+        position: absolute;
+        right: 0;
+        top: 50%;
+        transform: translateY(-50%);
+        z-index: 1;
+
+        li {
+          position: relative;
+          padding: 8px 15px;
+          cursor: pointer;
+
+          &.is-active {
+            &::before {
+              background-color: var(--van-primary-color);
+            }
+            &::after {
+              content: "";
+              position: absolute;
+              top: 50%;
+              left: 50%;
+              width: 17px;
+              height: 17px;
+              border-radius: 17px;
+              border: 2px solid var(--van-primary-color);
+              transform: translate(-50%, -50%);
+            }
+          }
+          &::before {
+            content: "";
+            position: relative;
+            display: block;
+            width: 10px;
+            height: 10px;
+            opacity: 0.5;
+            border-radius: 10px;
+            background-color: #918784;
+            z-index: 1;
+          }
+        }
+      }
+    }
+    &__footer {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      padding: 0 22px;
+      font-size: 19px;
+      color: var(--black-text-color);
+
+      div:first-child {
+        display: flex;
+        align-items: center;
+        font-weight: bold;
+
+        p {
+          flex: 1;
+          width: 0;
+          line-height: 24px;
+        }
+        span {
+          margin-left: 16px;
+          padding: 5px;
+          font-size: 12px;
+          color: #d2b986;
+          background: rgba(210, 185, 134, 0.3);
+          border-radius: 4px;
+          white-space: nowrap;
+          border: 1px solid #d2b986;
+        }
+      }
+      p:last-child {
+        margin-top: 4px;
+      }
+    }
+  }
+}
+.main {
+  margin-top: 20px;
+  padding: 22px 21px;
+  color: var(--black-text-color);
+  background: var(--white-bg);
+  border-radius: 10px;
+  box-shadow: 0px 8px 25px 0px rgba(146, 146, 146, 0.25);
+
+  :deep(.el-date-editor) {
+    width: 233px;
+    height: 45px;
+
+    .el-input__inner {
+      height: 45px;
+      line-height: 45px;
+      border-radius: 4px;
+    }
+  }
+  .el-button {
+    padding: 0 35px;
+    height: 45px;
+  }
+  &__header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding-bottom: 24px;
+    border-bottom: 1px dashed rgba(32, 32, 32, 0.5);
+  }
+  &__list {
+    position: relative;
+    min-height: 320px;
+
+    .visit8-card {
+      display: flex;
+      padding: 28px 23px;
+      height: 320px;
+      box-sizing: border-box;
+      border-bottom: 1px dashed rgba(32, 32, 32, 0.5);
+      cursor: pointer;
+
+      &__left {
+        flex: 0 0 185px;
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
+        font-size: 37px;
+        line-height: 48px;
+
+        img {
+          width: 50px;
+          height: 50px;
+        }
+      }
+      &__center {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
+        padding: 0 26px 0 69px;
+
+        > p {
+          font-size: 37px;
+          font-weight: bold;
+          line-height: 48px;
+        }
+        &__inner {
+          line-height: 18px;
+        }
+      }
+      &__right {
+        flex: 0 0 420px;
+        height: 268px;
+      }
+      &__tag {
+        display: inline-block;
+        padding: 8px 15px;
+        color: #d2b986;
+        background: rgba(210, 185, 134, 0.3);
+        border-radius: 4px;
+        border: 1px solid #d2b986;
+      }
+    }
+  }
+}
+.page {
+  display: flex;
+  justify-content: center;
+  padding-top: 30px;
+}

Різницю між файлами не показано, бо вона завелика
+ 370 - 228
src/views/Visit/Calendar/index.vue


+ 32 - 32
src/views/Visit/Guide/index.scss

@@ -1,32 +1,32 @@
-.Visit4 {
-  .conten {
-    color: black;
-    width: 1200px;
-    font-size: 18px;
-    line-height: 26px;
-    margin: auto;
-    .row {
-      width: 866px;
-      display: flex;
-      & > div {
-        width: 50%;
-        & > h3 {
-          padding-top: 30px;
-
-          font-size: 18px;
-          font-weight: 700;
-          margin-bottom: 20px;
-        }
-        & > p {
-          font-size: 14px;
-        }
-        & > img {
-          width: 100%;
-        }
-      }
-      .blank {
-        padding-left: 10px;
-      }
-    }
-  }
-}
+.Visit4 {
+  .conten {
+    color: black;
+    width: 1200px;
+    font-size: 18px;
+    line-height: 26px;
+    margin: auto;
+    :deep(.row) {
+      width: 866px;
+      display: flex;
+      & > div {
+        width: 50%;
+        & > h3 {
+          padding-top: 30px;
+
+          font-size: 18px;
+          font-weight: 700;
+          margin-bottom: 20px;
+        }
+        & > p {
+          font-size: 14px;
+        }
+        & > img {
+          width: 100%;
+        }
+      }
+      .blank {
+        padding-left: 10px;
+      }
+    }
+  }
+}

+ 17 - 72
src/views/Visit/Guide/index.vue

@@ -1,72 +1,17 @@
-<template>
-  <div
-    class="Visit4"
-    data-aria-viewport-area
-    aria-description="You have reached the content area under the Audio Guide & Tour page. To browse the content, use the tab key."
-  >
-    <div class="conten">
-      <div class="row">
-        <div>
-          <h3>Audio Guide</h3>
-          <p>The museum offers free audio guide.</p>
-          <p>
-            Visitors can get the devices from the audio guide cabinets with 200
-            RMB refundable deposit.
-          </p>
-          <p>
-            The cabinets can be found at several locations in the museum, such
-            as the east of the main entrance.
-          </p>
-          <p>The service is available in Chinese and English.</p>
-          <p>
-            For further questions, please visit the information desk for help.
-          </p>
-        </div>
-        <div>
-          <img
-            src="@/assets/images/Visit/Audio1.jpg"
-            alt="Audio Guide"
-            aria-description="Audio Guide"
-          />
-        </div>
-      </div>
-      <div class="row">
-        <div>
-          <img
-            src="@/assets/images/Visit/Audio2.jpg"
-            alt="Free Commentary Service"
-            aria-description="Free Commentary Service"
-          />
-        </div>
-        <div class="blank">
-          <h3>Free Commentary Service</h3>
-          <p>
-            Commentators provide free services on each open day (Tuesday to
-            Sunday).
-          </p>
-          <p>
-            Please check the specific time and exhibition location in the museum
-            on the day of your visit.
-          </p>
-        </div>
-      </div>
-      <div class="row">
-        <div>
-          <h3>Volunteer Guide</h3>
-          <p>Our museum volunteers offer free explanations and help.</p>
-        </div>
-        <div>
-          <img
-            src="@/assets/images/Visit/Audio3.jpg"
-            alt="Volunteer Guide"
-            aria-description="Volunteer Guide"
-          />
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-@import "./index.scss";
-</style>
+<template>
+  <div
+    class="Visit4"
+    data-aria-viewport-area
+    aria-description="You have reached the content area under the Audio Guide & Tour page. To browse the content, use the tab key."
+  >
+    <div class="conten" v-html="Visit.Guide.rtf" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { Visit } from "@/data";
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 43 - 36
src/views/Visit/Plans/index.scss

@@ -1,36 +1,43 @@
-.conten {
-  width: 100%;
-  background-color: var(--black-text-color);
-  height: 570px;
-  .box5 {
-    cursor: pointer;
-    width: 1200px;
-    padding: 15px 0 25px 0;
-    height: 62px;
-    margin: auto;
-    li {
-      color: white;
-      float: left;
-      width: 185px;
-      text-align: right;
-      font-size: 20px;
-      font-weight: bold;
-      &:nth-of-type(1) {
-        width: 62px;
-      }
-    }
-    .titleon {
-      background: url("@/assets/images/Floor/m-22.jpg") no-repeat right;
-      line-height: 62px;
-    }
-    .titleoff {
-      background: url("@/assets/images/Floor/m-23.jpg") no-repeat right;
-    }
-  }
-  .box1 {
-    margin: 50px auto 0px;
-    width: 1200px;
-    font-size: 18px;
-    line-height: 26px;
-  }
-}
+.conten {
+  width: 100%;
+  background-color: var(--black-text-color);
+  height: 570px;
+  .box5 {
+    cursor: pointer;
+    width: 1200px;
+    padding: 15px 0 25px 0;
+    height: 62px;
+    margin: auto;
+    li {
+      color: white;
+      float: left;
+      width: 185px;
+      text-align: right;
+      font-size: 20px;
+      font-weight: bold;
+      &:nth-of-type(1) {
+        width: 62px;
+      }
+      span {
+        padding-right: 18px;
+      }
+    }
+    .titleon {
+      background: url("@/assets/images/Floor/m-22.jpg") no-repeat right;
+      line-height: 62px;
+    }
+    .titleoff {
+      background: url("@/assets/images/Floor/m-23.jpg") no-repeat right;
+    }
+  }
+  .box1 {
+    margin: 50px auto 0px;
+    width: 1200px;
+    font-size: 18px;
+    line-height: 26px;
+
+    img {
+      width: 100%;
+    }
+  }
+}

+ 87 - 87
src/views/Visit/Plans/index.vue

@@ -1,87 +1,87 @@
-<template>
-  <div
-    class="Visit3"
-    data-aria-viewport-area
-    aria-description="You've reached the content area for the Floor Plans page; this area has seven images; please use the tab key to navigate through the content."
-  >
-    <div class="conten">
-      <div class="box5">
-        <ul>
-          <li
-            class="titleon"
-            v-for="(item, index) in topData"
-            :key="index"
-            :class="{
-              titleoff: index === topInd,
-            }"
-            @mouseenter="topInd = index"
-            @focus="topInd = index"
-            tabindex="0"
-            aria-label="Image link"
-            :aria-description="`Floor Plans ${item.label}`"
-          >
-            <span
-              :class="{
-                'aria-inverse-theme': index === topInd,
-              }"
-            >
-              {{ item.label }}&nbsp;&nbsp;&nbsp;
-            </span>
-          </li>
-        </ul>
-      </div>
-      <div class="box1">
-        <div v-for="(i, idx) in topData" :key="i.label">
-          <img :src="i.img" alt="" v-show="idx === topInd" />
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import { ref } from "vue";
-import Img1 from "@/assets/images/Floor/1.jpg";
-import Img2 from "@/assets/images/Floor/2.jpg";
-import Img3 from "@/assets/images/Floor/3.jpg";
-import Img4 from "@/assets/images/Floor/4.jpg";
-import Img5 from "@/assets/images/Floor/5.jpg";
-import Img6 from "@/assets/images/Floor/6.jpg";
-import Img7 from "@/assets/images/Floor/7.jpg";
-
-const topData = ref([
-  {
-    label: "B1",
-    img: Img1,
-  },
-  {
-    label: "F1",
-    img: Img2,
-  },
-  {
-    label: "F2",
-    img: Img3,
-  },
-  {
-    label: "F3",
-    img: Img4,
-  },
-  {
-    label: "F4",
-    img: Img5,
-  },
-  {
-    label: "F5",
-    img: Img6,
-  },
-  {
-    label: "F6",
-    img: Img7,
-  },
-]);
-const topInd = ref(0);
-</script>
-
-<style lang="scss" scoped>
-@import "./index.scss";
-</style>
+<template>
+  <div
+    class="Visit3"
+    data-aria-viewport-area
+    aria-description="You've reached the content area for the Floor Plans page; this area has seven images; please use the tab key to navigate through the content."
+  >
+    <div class="conten">
+      <div class="box5">
+        <ul>
+          <li
+            class="titleon"
+            v-for="(item, index) in topData"
+            :key="index"
+            :class="{
+              titleoff: index === topInd,
+            }"
+            @mouseenter="topInd = index"
+            @focus="topInd = index"
+            tabindex="0"
+            aria-label="Image link"
+            :aria-description="`Floor Plans ${item.label}`"
+          >
+            <span
+              :class="{
+                'aria-inverse-theme': index === topInd,
+              }"
+            >
+              {{ item.label }}
+            </span>
+          </li>
+        </ul>
+      </div>
+      <div class="box1">
+        <div v-for="(i, idx) in topData" :key="i.label">
+          <img :src="i.img" alt="" v-show="idx === topInd" />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref } from "vue";
+import Img1 from "@/assets/images/Floor/1.jpg";
+import Img2 from "@/assets/images/Floor/2.jpg";
+import Img3 from "@/assets/images/Floor/3.jpg";
+import Img4 from "@/assets/images/Floor/4.jpg";
+import Img5 from "@/assets/images/Floor/5.jpg";
+import Img6 from "@/assets/images/Floor/6.jpg";
+import Img7 from "@/assets/images/Floor/7.jpg";
+
+const topData = ref([
+  {
+    label: "B1",
+    img: Img1,
+  },
+  {
+    label: "F1",
+    img: Img2,
+  },
+  {
+    label: "F2",
+    img: Img3,
+  },
+  {
+    label: "F3",
+    img: Img4,
+  },
+  {
+    label: "F4",
+    img: Img5,
+  },
+  {
+    label: "F5",
+    img: Img6,
+  },
+  {
+    label: "F6",
+    img: Img7,
+  },
+]);
+const topInd = ref(0);
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 10 - 54
src/views/Visit/Reservation/components/Information.vue

@@ -1,54 +1,10 @@
-<template>
-  <div class="in-sidebar in-sidebar-three">
-    <p><br /></p>
-    <p tabindex="0">
-      <span style="font-family: arial, helvetica, sans-serif; font-size: 14px">
-        Ticket-reservation is subject to change when there is a large museum
-        event or a special opening ceremony for a new exhibition. The Capital
-        Museum will make a public announcement in advance. Please visit the
-        official website or inquire by telephone for detailed information.
-      </span>
-    </p>
-    <p><br /></p>
-    <p>
-      <span
-        tabindex="0"
-        style="font-family: arial, helvetica, sans-serif; font-size: 14px"
-      >
-        Official website:
-      </span>
-      <a
-        tabindex="0"
-        style="
-          font-size: 14px;
-          font-family: arial, helvetica, sans-serif;
-          color: rgb(255, 0, 0);
-        "
-      >
-        {{ origin }}
-      </a>
-      <span
-        tabindex="0"
-        style="font-family: arial, helvetica, sans-serif; font-size: 14px"
-      >
-        &nbsp; &nbsp; &nbsp; &nbsp;Phone:
-      </span>
-      <span
-        tabindex="0"
-        style="
-          font-size: 14px;
-          font-family: arial, helvetica, sans-serif;
-          color: rgb(255, 0, 0);
-        "
-      >
-        +86 (10) 63370491
-      </span>
-    </p>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import { ref } from "vue";
-
-const origin = ref(location.origin);
-</script>
+<template>
+  <div
+    class="in-sidebar in-sidebar-three"
+    v-html="Visit.Reservation.card[2].pp"
+  />
+</template>
+
+<script lang="ts" setup>
+import { Visit } from "@/data";
+</script>

+ 9 - 277
src/views/Visit/Reservation/components/Reservations.vue

@@ -1,277 +1,9 @@
-<template>
-  <div class="in-sidebar in-sidebar-one">
-    <p style="text-align: center"><br /></p>
-    <p>
-      <img
-        src="@/assets/images/Visit/pp1.jpg"
-        alt=""
-        tabindex="0"
-        aria-description="Ways of Reservation"
-      />
-    </p>
-    <p><br /></p>
-    <p>
-      <span tabindex="0"><strong>Ways of Reservation</strong></span>
-    </p>
-    <p><br /></p>
-    <p
-      tabindex="0"
-      :aria-description="`There are 3,600 daily personal booking places, available through website (3000 places) and telephone (600 places). The website ${origin} offers 24-hour service, and the telephone +86 (10) 63393339 service is available from 09:00 to 17:00. One person is only allowed to book one ticket.`"
-    >
-      <span style="font-family: arial, helvetica, sans-serif; font-size: 14px">
-        There are 3,600 daily personal booking places, available through website
-        (3000 places) and telephone (600 places). The website
-      </span>
-      <a
-        style="
-          font-family: arial, helvetica, sans-serif;
-          font-size: 14px;
-          text-decoration: none;
-          color: rgb(255, 0, 0);
-        "
-        :href="origin"
-        tabindex="0"
-      >
-        {{ origin }}
-      </a>
-      <span style="font-family: arial, helvetica, sans-serif; font-size: 14px">
-        offers&nbsp;24-hour&nbsp;
-      </span>
-      <span style="font-family: arial, helvetica, sans-serif; font-size: 14px">
-        service, and the&nbsp;telephone
-      </span>
-      <span
-        style="
-          font-family: arial, helvetica, sans-serif;
-          font-size: 14px;
-          color: rgb(255, 0, 0);
-        "
-      >
-        +86 (10) 63393339
-      </span>
-      <span style="font-family: arial, helvetica, sans-serif; font-size: 14px">
-        service is available from 09:00 to 17:00. One person is only allowed to
-        book one ticket.
-      </span>
-    </p>
-    <p><br /></p>
-    <p tabindex="0">
-      <span style="font-family: arial, helvetica, sans-serif; font-size: 14px"
-        >For group reservation, we offer 400 tickets a day. Please call<span
-          style="
-            font-size: 14px;
-            font-family: arial, helvetica, sans-serif;
-            color: rgb(255, 0, 0);
-          "
-          >+86 (10) 63370458&nbsp;&nbsp;</span
-        >between 09:00 to 17:00. Identity information of the group leader is
-        required.</span
-      >
-    </p>
-    <p><br /></p>
-    <p tabindex="0">
-      <span style="font-family: arial, helvetica, sans-serif; font-size: 14px"
-        >Reservation should be made at least one day in advance, and at most
-        seven days in advance.</span
-      >
-    </p>
-    <p><br /></p>
-    <table data-sort="sortDisabled">
-      <tbody>
-        <tr class="firstRow">
-          <td valign="top" colspan="2" rowspan="1">
-            <p style="text-align: center; height: 1px">
-              <img
-                src="@/assets/images/Visit/pp4.jpg"
-                style="max-width: 100%"
-              />
-            </p>
-          </td>
-        </tr>
-        <tr>
-          <td width="420" valign="top">
-            <p style="text-align: center">
-              <img
-                src="@/assets/images/Visit/pp2.jpg"
-                style="max-width: 100%"
-                tabindex="0"
-                aria-description="Way to Get Ticket"
-              />
-            </p>
-          </td>
-          <td width="420" valign="top">
-            <p>
-              <span style="font-family: arial, helvetica, sans-serif"
-                ><strong>&nbsp; &nbsp;&nbsp;</strong></span
-              >
-            </p>
-            <p tabindex="0">
-              <strong style="font-family: arial, helvetica, sans-serif"
-                ><span style="font-size: 18px">Way to Get Ticket</span></strong
-              >
-            </p>
-            <p><br /></p>
-            <p tabindex="0">
-              <span
-                style="
-                  font-size: 14px;
-                  font-family: arial, helvetica, sans-serif;
-                "
-              >
-                For personal visitors, please obtain the ticket at the service
-                center at the north door by showing your booking number and the
-                ID card used when the booking was made.
-              </span>
-            </p>
-            <p><br /></p>
-            <p tabindex="0">
-              <span
-                style="
-                  font-size: 14px;
-                  font-family: arial, helvetica, sans-serif;
-                "
-              >
-                For group visitors, the leader can obtain the ticket at the east
-                door of the ground floor with valid documents and introductory
-                letters.
-              </span>
-            </p>
-            <p><br /></p>
-          </td>
-        </tr>
-      </tbody>
-    </table>
-    <br />
-    <table data-sort="sortDisabled">
-      <tbody>
-        <tr class="firstRow">
-          <td valign="top" colspan="2" rowspan="1">
-            <p style="text-align: center; height: 1px">
-              <img
-                src="@/assets/images/Visit/pp4.jpg"
-                style="max-width: 100%"
-              />
-            </p>
-          </td>
-        </tr>
-        <tr>
-          <td width="420" valign="top">
-            <p>
-              <strong
-                style="
-                  font-family: arial, helvetica, sans-serif;
-                  font-size: 18px;
-                "
-                ><br
-              /></strong>
-            </p>
-            <p tabindex="0">
-              <strong
-                style="
-                  font-family: arial, helvetica, sans-serif;
-                  font-size: 18px;
-                "
-                >&nbsp; Entrance Time</strong
-              ><br />
-            </p>
-            <p><br /></p>
-            <p tabindex="0">
-              <span
-                style="
-                  font-family: arial, helvetica, sans-serif;
-                  font-size: 14px;
-                "
-                >From 09:00-16:00, Tuesday to Sunday.&nbsp;</span
-              >
-            </p>
-            <p tabindex="0">
-              <span
-                style="
-                  font-family: arial, helvetica, sans-serif;
-                  font-size: 14px;
-                "
-                >The museum is closed every Monday, except for
-                holidays.&nbsp;</span
-              >
-            </p>
-            <p tabindex="0">
-              <span
-                style="
-                  font-family: arial, helvetica, sans-serif;
-                  font-size: 14px;
-                "
-                >Please enter the museum before 16:00.</span
-              >
-            </p>
-          </td>
-          <td width="420" valign="top">
-            <p style="text-align: center">
-              <img
-                src="@/assets/images/Visit/pp3.jpg"
-                style="max-width: 100%"
-                tabindex="0"
-                aria-description="Entrance Time"
-              />
-            </p>
-          </td>
-        </tr>
-      </tbody>
-    </table>
-    <p tabindex="0">
-      <span style="font-family: arial, helvetica, sans-serif; font-size: 18px"
-        ><strong>Special Notices</strong></span
-      >
-    </p>
-    <p><br /></p>
-    <p tabindex="0">
-      <span style="font-family: arial, helvetica, sans-serif; font-size: 14px"
-        >1. One ticket is only for one person and the ticket is only valid on
-        the date printed.&nbsp;Please have the ticket checked at the
-        entrance.</span
-      >
-    </p>
-    <p tabindex="0">
-      <span style="font-family: arial, helvetica, sans-serif; font-size: 14px"
-        >2. Senior citizens (above 60) and handicapped persons can enter the
-        exhibition with valid documents even without reservation. Please ask
-        museum personnel for help.</span
-      ><br />
-    </p>
-    <div
-      tabindex="0"
-      aria-description="3. Space in the exhibition is limited, so museum may control the visitor numbers at any time to ensure orderly and pleasant viewing. Thanks for your understanding and cooperation."
-    >
-      <p>
-        <span
-          style="font-family: arial, helvetica, sans-serif; font-size: 14px"
-        >
-          3. Space in the exhibition is limited, so museum may control the
-          visitor numbers at any time to ensure orderly and pleasant viewing.
-          Thanks for your understanding and&nbsp;
-        </span>
-        <br />
-      </p>
-      <p>
-        <span
-          style="font-family: arial, helvetica, sans-serif; font-size: 14px"
-        >
-          &nbsp; &nbsp; cooperation.
-        </span>
-        <br />
-      </p>
-    </div>
-    <p tabindex="0">
-      <span style="font-family: arial, helvetica, sans-serif; font-size: 14px"
-        >4. The exhibition lasts for three months. Please keep this in mind when
-        planning a visit.</span
-      ><br />
-    </p>
-    <p><br /></p>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import { ref } from "vue";
-
-const origin = ref(location.origin);
-</script>
+<template>
+  <div class="in-sidebar in-sidebar-one" v-html="content" />
+</template>
+
+<script lang="ts" setup>
+import { Visit } from "@/data";
+
+const content = Visit.Reservation.card[0].pp;
+</script>

+ 10 - 98
src/views/Visit/Reservation/components/Visit.vue

@@ -1,98 +1,10 @@
-<template>
-  <div class="in-sidebar in-sidebar-two">
-    <table data-sort="sortDisabled">
-      <tbody>
-        <tr class="firstRow">
-          <td valign="top" colspan="2" rowspan="1">
-            <p style="text-align: center">
-              <img
-                src="@/assets/images/Visit/pp4.jpg"
-                style="max-width: 100%"
-              />
-            </p>
-          </td>
-        </tr>
-        <tr>
-          <td width="430" valign="top">
-            <p><br /></p>
-            <p tabindex="0">
-              <span
-                style="
-                  font-family: arial, helvetica, sans-serif;
-                  font-size: 14px;
-                "
-                >We encourage groups and travel agencies to make reservations by
-                telephone.</span
-              >
-            </p>
-            <p><br /></p>
-            <p tabindex="0">
-              <span
-                style="
-                  font-family: arial, helvetica, sans-serif;
-                  font-size: 14px;
-                "
-              >
-                Requirements: Name of the tour group, full name of the person
-                making the reservation, contact information and number of group
-                members.
-              </span>
-            </p>
-            <p><br /></p>
-            <p>
-              <span
-                tabindex="0"
-                style="
-                  font-family: arial, helvetica, sans-serif;
-                  font-size: 14px;
-                "
-              >
-                Telephone reservation (group visitors):
-                <span
-                  style="
-                    font-size: 14px;
-                    font-family: arial, helvetica, sans-serif;
-                    color: rgb(255, 0, 0);
-                  "
-                >
-                  +86 (10) 63370458&nbsp;
-                </span>
-              </span>
-            </p>
-            <p><br /><br /></p>
-            <p>
-              <span
-                style="
-                  color: rgb(255, 0, 0);
-                  font-family: arial, helvetica, sans-serif;
-                  font-size: 14px;
-                "
-                ><br
-              /></span>
-            </p>
-            <p style="text-align: center">
-              <img
-                src="@/assets/images/Visit/pp6.jpg"
-                style="max-width: 100%"
-                tabindex="0"
-                aria-description="Group Visit"
-              />
-            </p>
-          </td>
-          <td width="430" valign="top">
-            <p style="text-align: center"><br /></p>
-            <p>
-              <img
-                src="@/assets/images/Visit/pp5.jpg"
-                style="max-width: 100%"
-                tabindex="0"
-                aria-description="Group Visit"
-              /><br />
-            </p>
-          </td>
-        </tr>
-      </tbody>
-    </table>
-    <p><br /></p>
-  </div>
-</template>
+<template>
+  <div
+    class="in-sidebar in-sidebar-two"
+    v-html="Visit.Reservation.card[1].pp"
+  />
+</template>
+
+<script lang="ts" setup>
+import { Visit } from "@/data";
+</script>

+ 141 - 141
src/views/Visit/Reservation/index.scss

@@ -1,141 +1,141 @@
-.reservation {
-  margin-bottom: 20px;
-
-  &-container {
-    width: 1180px;
-    margin: auto;
-  }
-
-  strong {
-    font-weight: 700;
-    font-size: 16px;
-  }
-
-  .public {
-    overflow: hidden;
-    transition: all 0.3s;
-    height: 39px;
-    background-color: rgba(206, 36, 69, 0.97);
-    width: 100%;
-    cursor: pointer;
-    position: absolute;
-    left: 0;
-    top: 60px;
-    & > div {
-      width: 1180px;
-      margin: 0 auto;
-      padding: 5px 0;
-      font-size: 18px;
-      line-height: 1.6;
-      color: #fff;
-      & > span {
-        float: right;
-      }
-    }
-  }
-  .hintShow {
-    height: 0px;
-  }
-  .mm1 {
-    height: 1%;
-    overflow: hidden;
-    padding-bottom: 30px;
-    .mm1l {
-      width: 550px;
-      float: left;
-      font-size: 18px;
-      line-height: 28px;
-      & > p {
-        font-size: 14px;
-      }
-    }
-    .mm1r {
-      width: 590px;
-      float: right;
-      padding-top: 43px;
-      & > p {
-        padding: 15px;
-        font-size: 14px;
-        line-height: 28px;
-      }
-      & > h2 {
-        cursor: pointer;
-        clear: both;
-        background-color: #c90006;
-        text-align: center;
-        height: 30px;
-        line-height: 30px;
-        color: #fff;
-        font-weight: normal;
-      }
-    }
-  }
-
-  .collapse {
-    --el-collapse-content-bg-color: var(--white-bg);
-    --el-collapse-content-text-color: var(--black-text-color);
-
-    :deep(.el-collapse-item) {
-      margin-bottom: 1px;
-
-      &:nth-child(1) .el-collapse-item__header {
-        background: url("@/assets/images/Visit/m-28.jpg");
-      }
-      &:nth-child(2) .el-collapse-item__header {
-        background: url("@/assets/images/Visit/m-29.jpg");
-      }
-      &:nth-child(3) .el-collapse-item__header {
-        background: url("@/assets/images/Visit/m-30.jpg");
-      }
-      &.is-active {
-        .el-collapse-item__header::after {
-          background-image: url("@/assets/images/Visit/m-32.png");
-        }
-      }
-      .el-collapse-item__header {
-        position: relative;
-        padding: 0 10px;
-        height: 50px;
-        border: none;
-        color: white;
-        font-size: 24px;
-
-        &::after {
-          content: "";
-          position: absolute;
-          top: 50%;
-          right: 30px;
-          width: 14px;
-          height: 14px;
-          background: url("@/assets/images/Visit/m-31.png") no-repeat center /
-            contain;
-          transform: translateY(-50%);
-        }
-      }
-      .el-collapse-item__arrow {
-        display: none;
-      }
-    }
-
-    .in-sidebar {
-      padding: 0 20px;
-      font-size: 18px;
-      line-height: 28px;
-
-      .firstRow {
-        p {
-          height: 1px;
-        }
-      }
-      :deep(strong) {
-        font-weight: 700;
-        font-size: 16px;
-      }
-      :deep(img) {
-        border: none;
-        vertical-align: top;
-        max-width: 100%;
-      }
-    }
-  }
-}
+.reservation {
+  margin-bottom: 20px;
+
+  &-container {
+    width: 1180px;
+    margin: auto;
+  }
+
+  strong {
+    font-weight: 700;
+    font-size: 16px;
+  }
+
+  .public {
+    overflow: hidden;
+    transition: all 0.3s;
+    height: 39px;
+    background-color: rgba(206, 36, 69, 0.97);
+    width: 100%;
+    cursor: pointer;
+    position: absolute;
+    left: 0;
+    top: 60px;
+    & > div {
+      width: 1180px;
+      margin: 0 auto;
+      padding: 5px 0;
+      font-size: 18px;
+      line-height: 1.6;
+      color: #fff;
+      & > span {
+        float: right;
+      }
+    }
+  }
+  .hintShow {
+    height: 0px;
+  }
+  :deep(.mm1) {
+    height: 1%;
+    overflow: hidden;
+    padding-bottom: 30px;
+    .mm1l {
+      width: 550px;
+      float: left;
+      font-size: 18px;
+      line-height: 28px;
+      & > p {
+        font-size: 14px;
+      }
+    }
+    .mm1r {
+      width: 590px;
+      float: right;
+      padding-top: 43px;
+      & > p {
+        padding: 15px;
+        font-size: 14px;
+        line-height: 28px;
+      }
+      & > h2 {
+        cursor: pointer;
+        clear: both;
+        background-color: #c90006;
+        text-align: center;
+        height: 30px;
+        line-height: 30px;
+        color: #fff;
+        font-weight: normal;
+      }
+    }
+  }
+
+  .collapse {
+    --el-collapse-content-bg-color: var(--white-bg);
+    --el-collapse-content-text-color: var(--black-text-color);
+
+    :deep(.el-collapse-item) {
+      margin-bottom: 1px;
+
+      &:nth-child(1) .el-collapse-item__header {
+        background: url("@/assets/images/Visit/m-28.jpg");
+      }
+      &:nth-child(2) .el-collapse-item__header {
+        background: url("@/assets/images/Visit/m-29.jpg");
+      }
+      &:nth-child(3) .el-collapse-item__header {
+        background: url("@/assets/images/Visit/m-30.jpg");
+      }
+      &.is-active {
+        .el-collapse-item__header::after {
+          background-image: url("@/assets/images/Visit/m-32.png");
+        }
+      }
+      .el-collapse-item__header {
+        position: relative;
+        padding: 0 10px;
+        height: 50px;
+        border: none;
+        color: white;
+        font-size: 24px;
+
+        &::after {
+          content: "";
+          position: absolute;
+          top: 50%;
+          right: 30px;
+          width: 14px;
+          height: 14px;
+          background: url("@/assets/images/Visit/m-31.png") no-repeat center /
+            contain;
+          transform: translateY(-50%);
+        }
+      }
+      .el-collapse-item__arrow {
+        display: none;
+      }
+    }
+
+    .in-sidebar {
+      padding: 0 20px;
+      font-size: 18px;
+      line-height: 28px;
+
+      .firstRow {
+        p {
+          height: 1px;
+        }
+      }
+      :deep(strong) {
+        font-weight: 700;
+        font-size: 16px;
+      }
+      :deep(img) {
+        border: none;
+        vertical-align: top;
+        max-width: 100%;
+      }
+    }
+  }
+}

+ 37 - 70
src/views/Visit/Reservation/index.vue

@@ -1,70 +1,37 @@
-<template>
-  <div
-    class="reservation"
-    data-aria-viewport-area
-    aria-description="You've reached the content area for the Reservation page. This area contains three parts of content. Please press the tab key to browse the information."
-  >
-    <div class="reservation-container">
-      <div class="mm1">
-        <div class="mm1l">
-          <p>
-            <img src="@/components/PageTitle/pLeft.jpg" />
-            <span>&nbsp;<strong>How to Make a Reservation?</strong></span>
-          </p>
-          <p><br /></p>
-          <p>Telephone Reservation:</p>
-          <p>
-            •&nbsp; Individual visitors:&nbsp;<span
-              style="
-                color: rgb(255, 0, 0);
-                font-family: arial, helvetica, sans-serif;
-                font-size: 14px;
-              "
-              >+86 (10) 63393339</span
-            >
-          </p>
-          <p>
-            •&nbsp; Group visitors:&nbsp;<span
-              style="font-size: 14px; color: rgb(255, 0, 0)"
-              >+86 (10) 63370458</span
-            >
-          </p>
-          <p>From 9:00 to 17:00 every day.</p>
-          <p><br /></p>
-        </div>
-        <div class="mm1r">
-          <p>
-            Either system will issue a confirmation number. Visitors will be
-            required to show the number and valid ID in order to receive free
-            entrance tickets on the day of their visit.
-          </p>
-        </div>
-      </div>
-
-      <ElCollapse v-model="activeName" accordion class="collapse">
-        <ElCollapseItem title="Guidelines for Reservations" name="reservations">
-          <Reservations />
-        </ElCollapseItem>
-        <ElCollapseItem title="Group Visit" name="visit">
-          <Visit />
-        </ElCollapseItem>
-        <ElCollapseItem title="Relevant Information" name="information">
-          <Information />
-        </ElCollapseItem>
-      </ElCollapse>
-    </div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import { ref } from "vue";
-import Reservations from "./components/Reservations.vue";
-import Visit from "./components/Visit.vue";
-import Information from "./components/Information.vue";
-
-const activeName = ref("reservations");
-</script>
-
-<style lang="scss" scoped>
-@import "./index.scss";
-</style>
+<template>
+  <div
+    class="reservation"
+    data-aria-viewport-area
+    aria-description="You've reached the content area for the Reservation page. This area contains three parts of content. Please press the tab key to browse the information."
+  >
+    <div class="reservation-container">
+      <div v-html="Visit.Reservation.rtf" />
+
+      <ElCollapse v-model="activeName" accordion class="collapse">
+        <ElCollapseItem title="Guidelines for Reservations" name="reservations">
+          <Reservations />
+        </ElCollapseItem>
+        <ElCollapseItem title="Group Visit" name="visit">
+          <VisitComp />
+        </ElCollapseItem>
+        <ElCollapseItem title="Relevant Information" name="information">
+          <Information />
+        </ElCollapseItem>
+      </ElCollapse>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref } from "vue";
+import Reservations from "./components/Reservations.vue";
+import VisitComp from "./components/Visit.vue";
+import Information from "./components/Information.vue";
+import { Visit } from "@/data";
+
+const activeName = ref("reservations");
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 36 - 36
src/views/Visit/Shop/index.scss

@@ -1,36 +1,36 @@
-.Visit6 {
-  .conten {
-    color: #000;
-    width: 1200px;
-    font-size: 18px;
-    line-height: 26px;
-    margin: auto;
-    .row {
-      width: 866px;
-      display: flex;
-      & > div {
-        width: 50%;
-        & > h3 {
-          padding-top: 30px;
-
-          font-size: 18px;
-          font-weight: 700;
-          margin-bottom: 20px;
-        }
-        & > p {
-          font-size: 14px;
-        }
-        & > img {
-          width: 100%;
-        }
-      }
-      .blank {
-        padding-left: 10px;
-      }
-    }
-    .buy {
-      margin: 30px 0 10px;
-      text-align: center;
-    }
-  }
-}
+.Visit6 {
+  .conten {
+    color: #000;
+    width: 1200px;
+    font-size: 18px;
+    line-height: 26px;
+    margin: auto;
+    :deep(.row) {
+      width: 866px;
+      display: flex;
+      & > div {
+        width: 50%;
+        & > h3 {
+          padding-top: 30px;
+
+          font-size: 18px;
+          font-weight: 700;
+          margin-bottom: 20px;
+        }
+        & > p {
+          font-size: 14px;
+        }
+        & > img {
+          width: 100%;
+        }
+      }
+      .blank {
+        padding-left: 10px;
+      }
+    }
+  }
+  .buy {
+    margin: 30px 0 10px;
+    text-align: center;
+  }
+}

+ 21 - 57
src/views/Visit/Shop/index.vue

@@ -1,57 +1,21 @@
-<template>
-  <div
-    class="Visit6"
-    data-aria-viewport-area
-    aria-description="You have reached the content area of the Café & Shop page. To browse the content, please use the tab key."
-  >
-    <div class="conten">
-      <div class="row">
-        <div>
-          <h3>Cafeteria</h3>
-          <p>
-            The cafeteria is located on the northeast corner of the round
-            exhibition room at basement level. Covering about 200 square meters,
-            it offers coffee, soft drinks and low alcohol beverages.
-          </p>
-        </div>
-        <div>
-          <img
-            src="@/assets/images/Visit/cafe1.jpg"
-            alt="Cafeteria"
-            aria-description="Cafeteria"
-          />
-        </div>
-      </div>
-      <div class="row">
-        <div>
-          <img
-            src="@/assets/images/Visit/cafe2.jpg"
-            alt="Shop"
-            aria-description="Shop"
-          />
-        </div>
-        <div class="blank">
-          <h3>Shop</h3>
-          <p>
-            Located in the southwest of the museum's basement and with a floor
-            space of about 550 square meters, the souvenir shop offers a wide
-            variety of souvenirs including special souvenirs from the museum,
-            books, and audio-visual products. Also on sale here are various arts
-            and crafts with local characteristics, gold and jade ornaments,
-            collotype calligraphic works and paintings of great collecting
-            value, and other arts and crafts with unique features. The thousands
-            of books on sale here mainly have a "Beijing taste" or are books on
-            collecting and connoisseurship.
-          </p>
-        </div>
-      </div>
-      <div class="buy">
-        <img src="@/assets/images/Visit/cafeBuy.jpg" alt="" />
-      </div>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-@import "./index.scss";
-</style>
+<script setup lang="ts">
+import { Visit } from "@/data";
+</script>
+
+<template>
+  <div
+    class="Visit6"
+    data-aria-viewport-area
+    aria-description="You have reached the content area of the Café & Shop page. To browse the content, please use the tab key."
+  >
+    <div class="conten" v-html="Visit.Shop.rtf" />
+
+    <div class="buy">
+      <img src="@/assets/images/Visit/cafeBuy.jpg" alt="" />
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 11 - 1
src/views/Visit/index.vue

@@ -3,7 +3,7 @@
     <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"
+      :src="bannerUrl"
     />
 
     <div class="container">
@@ -54,6 +54,8 @@ 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";
+import { getBaseURL } from "@dage/service";
+import { useBaseStore } from "@/stores/base";
 
 const NAV_LIST = [
   {
@@ -91,6 +93,14 @@ const NAV_LIST = [
   },
 ];
 
+const baseUrl = getBaseURL();
+const baseStore = useBaseStore();
+const bannerUrl = computed(() => {
+  return (
+    baseUrl + baseStore.bannerList.find((i) => i.name === "Visit")?.thumbPc
+  );
+});
+
 const route = useRoute();
 const curRoute = computed(() =>
   NAV_LIST.find((i) => i.pathName === (route.name as string))