chenlei 1 tahun lalu
induk
melakukan
49c2742292
55 mengubah file dengan 1281 tambahan dan 238 penghapusan
  1. 1 2
      components.d.ts
  2. 1 0
      package.json
  3. 8 0
      pnpm-lock.yaml
  4. 9 1
      src/api/collections.ts
  5. 5 1
      src/api/events.ts
  6. 5 1
      src/api/learn.ts
  7. 9 1
      src/api/publications.ts
  8. 6 1
      src/components/ImgCard.vue
  9. 17 1
      src/components/Layout/components/Search/index.vue
  10. 6 2
      src/components/Layout/components/Sidebar/index.vue
  11. 5 1
      src/components/Layout/index.vue
  12. 2 2
      src/components/Loading.vue
  13. 90 19
      src/data.ts
  14. 2 1
      src/main.ts
  15. 19 0
      src/router/index.ts
  16. 20 0
      src/stores/collection.ts
  17. 0 12
      src/stores/counter.ts
  18. TEMPAT SAMPAH
      src/views/About/images/bgA.png
  19. TEMPAT SAMPAH
      src/views/About/images/bgAD.png
  20. 12 1
      src/views/About/index.vue
  21. 6 0
      src/views/Collections/Detail/index.scss
  22. 34 6
      src/views/Collections/Detail/index.vue
  23. 10 5
      src/views/Collections/List/index.vue
  24. 17 20
      src/views/Collections/index.vue
  25. 15 0
      src/views/Events/Detail/index.scss
  26. 67 11
      src/views/Events/Detail/index.vue
  27. 3 1
      src/views/Events/index.scss
  28. 55 6
      src/views/Events/index.vue
  29. 1 1
      src/views/Exhibitions/Current/index.vue
  30. TEMPAT SAMPAH
      src/views/JoinSupport/images/bgD.png
  31. 15 4
      src/views/JoinSupport/index.vue
  32. 2 4
      src/views/Learn/Adults/index.vue
  33. 12 0
      src/views/Learn/Detail/index.scss
  34. 70 11
      src/views/Learn/Detail/index.vue
  35. 2 4
      src/views/Learn/Families/index.vue
  36. 2 4
      src/views/Learn/Students/index.vue
  37. 41 8
      src/views/Learn/components/Card.vue
  38. 60 0
      src/views/Learn/components/List.vue
  39. TEMPAT SAMPAH
      src/views/Learn/images/bgLI.png
  40. 15 5
      src/views/Learn/index.vue
  41. 14 0
      src/views/Publications/Catalogues/index.scss
  42. 60 1
      src/views/Publications/Catalogues/index.vue
  43. 30 0
      src/views/Publications/Detail/index.scss
  44. 48 0
      src/views/Publications/Detail/index.vue
  45. 68 15
      src/views/Publications/Magazines/components/TimeLinePanel.vue
  46. TEMPAT SAMPAH
      src/views/Publications/images/bgPu.png
  47. TEMPAT SAMPAH
      src/views/Publications/images/bgPuD.png
  48. 15 4
      src/views/Publications/index.vue
  49. 36 0
      src/views/Publications/pdf.vue
  50. TEMPAT SAMPAH
      src/views/Search/images/bannerRes.png
  51. 47 0
      src/views/Search/index.scss
  52. 304 0
      src/views/Search/index.vue
  53. 5 58
      src/views/VisitInfo/components/Guidelines/index.vue
  54. 5 10
      src/views/VisitInfo/components/Information/index.vue
  55. 5 14
      src/views/VisitInfo/components/Visit/index.vue

+ 1 - 2
components.d.ts

@@ -8,7 +8,6 @@ export {}
 declare module 'vue' {
   export interface GlobalComponents {
     Calendar: typeof import('./src/components/Calendar.vue')['default']
-    Card: typeof import('./src/components/Card.vue')['default']
     DateTable: typeof import('./src/components/DateTable/index.vue')['default']
     ImgCard: typeof import('./src/components/ImgCard.vue')['default']
     ImgSwiper: typeof import('./src/components/ImgSwiper.vue')['default']
@@ -32,10 +31,10 @@ declare module 'vue' {
     VanImagePreview: typeof import('vant/es')['ImagePreview']
     VanList: typeof import('vant/es')['List']
     VanLoading: typeof import('vant/es')['Loading']
+    VanNavBar: typeof import('vant/es')['NavBar']
     VanNoticeBar: typeof import('vant/es')['NoticeBar']
     VanPagination: typeof import('vant/es')['Pagination']
     VanPopup: typeof import('vant/es')['Popup']
-    VanSticky: typeof import('vant/es')['Sticky']
     VanSwipe: typeof import('vant/es')['Swipe']
     VanSwipeItem: typeof import('vant/es')['SwipeItem']
     VanTab: typeof import('vant/es')['Tab']

+ 1 - 0
package.json

@@ -16,6 +16,7 @@
     "@vue/shared": "^3.4.27",
     "@vueuse/core": "^10.11.0",
     "lodash-unified": "^1.0.3",
+    "pdfh5": "1.4.2",
     "pinia": "^2.1.7",
     "vant": "^4.9.0",
     "vue": "^3.4.21",

+ 8 - 0
pnpm-lock.yaml

@@ -23,6 +23,9 @@ importers:
       lodash-unified:
         specifier: ^1.0.3
         version: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21)
+      pdfh5:
+        specifier: 1.4.2
+        version: 1.4.2
       pinia:
         specifier: ^2.1.7
         version: 2.1.7(typescript@5.4.5)(vue@3.4.27(typescript@5.4.5))
@@ -1568,6 +1571,9 @@ packages:
   pathe@1.1.2:
     resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
 
+  pdfh5@1.4.2:
+    resolution: {integrity: sha512-1BL8HIx/EEZowRPBgas7/WokbGEv1gxKNRmmHSimG113178mKxIBH4pxWBc0tj6d25Sy+EwnlQwv9cUUmQa42w==}
+
   picocolors@1.0.1:
     resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
 
@@ -3610,6 +3616,8 @@ snapshots:
 
   pathe@1.1.2: {}
 
+  pdfh5@1.4.2: {}
+
   picocolors@1.0.1: {}
 
   picomatch@2.3.1: {}

+ 9 - 1
src/api/collections.ts

@@ -38,5 +38,13 @@ export const getCollectionListApi = (params: CollectionListParams) => {
 };
 
 export const getCollectionDetailApi = (id: number) => {
-  return requestByGet<CollectionDetail>(`/api/show/collection/detail/${id}`);
+  return requestByGet<CollectionDetail>(
+    `/api/show/collection/detail/${id}`,
+    undefined,
+    {
+      meta: {
+        showLoading: true,
+      },
+    }
+  );
 };

+ 5 - 1
src/api/events.ts

@@ -26,7 +26,11 @@ export const getEventListApi = (params: EventListParams) => {
 };
 
 export const getEventDetailApi = (id: number | string) => {
-  return requestByGet<EventDetail>(`/api/show/event/detail/${id}`);
+  return requestByGet<EventDetail>(`/api/show/event/detail/${id}`, undefined, {
+    meta: {
+      showLoading: true,
+    },
+  });
 };
 
 export interface RecommendEventListParams {

+ 5 - 1
src/api/learn.ts

@@ -28,5 +28,9 @@ export const getLearnPageListApi = (params: LearnPageListParams) => {
 };
 
 export const getLearnDetailApi = (id: string | number) => {
-  return requestByGet<LearnDetail>(`/api/show/learn/detail/${id}`);
+  return requestByGet<LearnDetail>(`/api/show/learn/detail/${id}`, undefined, {
+    meta: {
+      showLoading: true,
+    },
+  });
 };

+ 9 - 1
src/api/publications.ts

@@ -38,5 +38,13 @@ export const getPublishListApi = async (params: PublishListParams) => {
 };
 
 export const getPublishDetailApi = async (id: string | number) => {
-  return requestByGet<PublicationDetail>(`/api/show/publish/detail/${id}`);
+  return requestByGet<PublicationDetail>(
+    `/api/show/publish/detail/${id}`,
+    undefined,
+    {
+      meta: {
+        showLoading: true,
+      },
+    }
+  );
 };

+ 6 - 1
src/components/ImgCard.vue

@@ -3,7 +3,12 @@
     class="img-card"
     @click="$router.push({ name: 'ExDetail', query: { id: item.id } })"
   >
-    <VanImage class="img-card__img" :height="160" :src="baseUrl + item.thumb" />
+    <VanImage
+      class="img-card__img"
+      lazy-load
+      :height="160"
+      :src="baseUrl + item.thumb"
+    />
 
     <div class="img-card__inner">
       <p class="limit-line line-4">

+ 17 - 1
src/components/Layout/components/Search/index.vue

@@ -2,7 +2,21 @@
   <VanPopup v-model:show="show" class="layout-search" :overlay="false">
     <div class="layout-search-header">
       <div class="layout-search-header__input">
-        <input placeholder="search......" />
+        <input
+          type="search"
+          v-model="keyword"
+          placeholder="search......"
+          @keyup.enter="
+            () => {
+              $router.push({
+                name: 'Search',
+                query: { keyword: encodeURIComponent(keyword) },
+              });
+              show = false;
+              keyword = '';
+            }
+          "
+        />
       </div>
 
       <div class="layout-search-header__close" @click="show = false">
@@ -28,6 +42,8 @@ const show = computed({
     emits("update:visible", v);
   },
 });
+
+const keyword = ref("");
 </script>
 
 <style lang="scss" scoped>

+ 6 - 2
src/components/Layout/components/Sidebar/index.vue

@@ -14,7 +14,11 @@
         src="../../images/zhong2.png"
         @click="emits('goToCNWeb')"
       />
-      <img class="sidebar-header__search" src="../../images/search2.png" />
+      <img
+        class="sidebar-header__search"
+        src="../../images/search2.png"
+        @click="emits('openSearch')"
+      />
     </div>
 
     <div class="sidebar-main">
@@ -58,7 +62,7 @@ import { useRouter } from "vue-router";
 const props = defineProps<{
   visible: boolean;
 }>();
-const emits = defineEmits(["update:visible", "goToCNWeb"]);
+const emits = defineEmits(["update:visible", "goToCNWeb", "openSearch"]);
 
 const router = useRouter();
 const activeCollapse = ref();

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

@@ -71,7 +71,11 @@
     </div>
   </div>
 
-  <Sidebar v-model:visible="sidebarVisible" @goToCNWeb="goToCNWeb" />
+  <Sidebar
+    v-model:visible="sidebarVisible"
+    @goToCNWeb="goToCNWeb"
+    @openSearch="searchVisible = true"
+  />
   <Search v-model:visible="searchVisible" />
 </template>
 

+ 2 - 2
src/components/Loading.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="loading">
-    <VanLoading color="white" />
+    <VanLoading color="var(--van-primary-color)" />
   </div>
 </template>
 
@@ -23,7 +23,7 @@
     left: 0;
     right: 0;
     bottom: 0;
-    background: var(--van-overlay-background);
+    background: rgba(255, 255, 255, 0.8);
     z-index: -1;
   }
 }

+ 90 - 19
src/data.ts

@@ -6,31 +6,102 @@ import access2 from "@/views/Visit/components/Accessibility/images/access2.jpg";
 import access3 from "@/views/Visit/components/Accessibility/images/access3.jpg";
 import cafe1 from "@/views/Visit/components/Cafe/images/cafe1.jpg";
 import cafe2 from "@/views/Visit/components/Cafe/images/cafe2.jpg";
+import pp1 from "@/views/VisitInfo/images/pp1.jpg";
+import pp2 from "@/views/VisitInfo/images/pp2.jpg";
+import pp3 from "@/views/VisitInfo/images/pp3.jpg";
+import pp5 from "@/views/VisitInfo/images/pp5.jpg";
+import pp6 from "@/views/VisitInfo/images/pp6.jpg";
 
 export const Visit = {
   Reservation: {
     name: "How to Make a Reservation",
     module: "visit",
     type: "reservation",
-    rtf: `
-      <div class="mm1"><div class="mm1l"><p><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>
-    `,
+    rtf: "",
+    children: {
+      Guidelines: `
+        <div class="title">Ways of Reservation</div>
+        <img src="${pp1}" alt="" />
+        <p>
+          There are 3,600 daily personal booking places, available through website
+          (3000 places) and telephone (600 places). The website
+          <a href="#/" class="indexUrl"
+            >https://en.capitalmuseum.org.cn/#/Layout/Home</a
+          >
+          offers 24-hour service, and the telephone <br />+86 (10) 63393339 service
+          is available from 09:00 to 17:00. One person is only allowed to book one
+          ticket.
+        </p>
+        <p>
+          For group reservation, we offer 400 tickets a day. Please call +86 (10)
+          63370458 between 09:00 to 17:00. Identity information of the group leader
+          is required.
+        </p>
+        <p>
+          Reservation should be made at least one day in advance, and at most seven
+          days in advance.
+        </p>
+        <div class="title">Way to Get Ticket</div>
+        <img src="${pp2}" alt="" />
+        <p>
+          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.
+        </p>
+        <p>
+          For group visitors, the leader can obtain the ticket at the east door of
+          the ground floor with valid documents and introductory letters.
+        </p>
+        <div class="title">Entrance Time</div>
+        <img src="${pp3}" alt="" />
+        <p>From 09:00-16:00, Tuesday to Sunday.</p>
+        <p>The museum is closed every Monday, except for holidays.</p>
+        <p>Please enter the museum before 16:00.</p>
+        <div class="title">Special Notices</div>
+        <p>
+          1. One ticket is only for one person and the ticket is only valid on the
+          date printed. Please have the ticket checked at the entrance.
+        </p>
+        <p>
+          2. Senior citizens (above 60) and handicapped persons can enter the
+          exhibition with valid documents even without reservation. Please ask
+          museum personnel for help.
+        </p>
+        <p>
+          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>
+        <p>
+          4. The exhibition lasts for three months. Please keep this in mind when
+          planning a visit.
+        </p>
+      `,
+      Information: `
+        <p>
+          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 publicannouncement in advance. Please visit the official
+          website or inquire by telephone for detailed information.
+        </p>
+        <p>Official website: https://en.capitalmuseum.org.cn/#/Layout/Home</p>
+        <p>Phone: +86 (10) 63370491</p>
+      `,
+      Visit: `
+        <img src="${pp5}" alt="" />
+
+        <p>
+          We encourage groups and travel agencies to make reservations by telephone.
+        </p>
+        <p>
+          Requirements: Name of the tour group, full name of the person making the
+          reservation,
+        </p>
+        <p>contact information and number of group members.</p>
+        <p>Telephone reservation (group visitors): +86 (10) 63370458</p>
+        <img src="${pp6}" alt="" />
+      `,
+    },
   },
   Guide: {
     name: "Audio Guide & Tour",

+ 2 - 1
src/main.ts

@@ -5,7 +5,7 @@ import { createPinia } from "pinia";
 
 import App from "./App.vue";
 import router from "./router";
-import { Locale, Notify } from "vant";
+import { Locale, Notify, Lazyload } from "vant";
 import Loading from "@/components/Loading.vue";
 import enUS from "vant/es/locale/lang/en-US";
 import "vant/lib/notify/index.css";
@@ -26,6 +26,7 @@ Locale.use("en-US", enUS);
 app.use(createPinia());
 app.use(router);
 app.use(Notify);
+app.use(Lazyload);
 // @ts-ignore
 app.use(Vue3TouchEvents);
 app.component("svg-icon", svgIcon);

+ 19 - 0
src/router/index.ts

@@ -174,6 +174,12 @@ const router = createRouter({
             },
           ],
         },
+        {
+          path: "/Layout/Publications/Info",
+          name: "PublicationDetail",
+          component: () => import("../views/Publications/Detail/index.vue"),
+          meta: { navBgColor: "rgba(110,148,141,.9)" },
+        },
         // Join页面
         {
           path: "/Layout/Join",
@@ -257,8 +263,21 @@ const router = createRouter({
           },
           component: () => import("../views/Events/Detail/index.vue"),
         },
+        {
+          path: "/Layout/Search",
+          name: "Search",
+          meta: {
+            navBgColor: "#bd7739",
+          },
+          component: () => import("../views/Search/index.vue"),
+        },
       ],
     },
+    {
+      path: "/Layout/Pdf",
+      name: "Pdf",
+      component: () => import("../views/Publications/pdf.vue"),
+    },
   ],
 });
 

+ 20 - 0
src/stores/collection.ts

@@ -0,0 +1,20 @@
+import { onMounted, ref } from "vue";
+import { defineStore } from "pinia";
+import { getCollectionThumbListApi, type CollectionThumbListItem } from "@/api";
+
+export const useCollectionStore = defineStore("collection", () => {
+  const thumbList = ref<CollectionThumbListItem[]>([]);
+
+  onMounted(() => {
+    getThumbList();
+  });
+
+  const getThumbList = async () => {
+    const data = await getCollectionThumbListApi();
+    thumbList.value = data;
+
+    return data;
+  };
+
+  return { thumbList, getThumbList };
+});

+ 0 - 12
src/stores/counter.ts

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

TEMPAT SAMPAH
src/views/About/images/bgA.png


TEMPAT SAMPAH
src/views/About/images/bgAD.png


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

@@ -1,6 +1,6 @@
 <template>
   <div class="about">
-    <PageBanner title="About" :img="BannerImg" />
+    <PageBanner title="About" :img="bannerUrl || BannerImg" />
 
     <RouterView />
   </div>
@@ -9,4 +9,15 @@
 <script lang="ts" setup>
 import PageBanner from "@/components/PageBanner.vue";
 import BannerImg from "./images/bannerA.png";
+import { getBaseURL } from "@dage/service";
+import { useBaseStore } from "@/stores/base";
+import { computed } from "vue";
+
+const baseUrl = getBaseURL();
+const baseStore = useBaseStore();
+const bannerUrl = computed(() => {
+  return (
+    baseUrl + baseStore.bannerList.find((i) => i.name === "About")?.thumbApp
+  );
+});
 </script>

+ 6 - 0
src/views/Collections/Detail/index.scss

@@ -10,5 +10,11 @@
       background: url("@/assets/images/chosen.png") no-repeat left 8px / 44px
         36px;
     }
+    :deep(p) {
+      margin-bottom: 30px;
+      font-size: 32px;
+      line-height: 36px;
+      color: #6a6a6a;
+    }
   }
 }

+ 34 - 6
src/views/Collections/Detail/index.vue

@@ -1,18 +1,46 @@
 <template>
   <div class="collections-detail">
-    <VanImage
-      src="https://en.capitalmuseum.org.cn/data/Collections/Ceramics/big1.png"
-      style="display: block"
-    />
+    <VanImage :src="imgUrl" style="display: block" />
 
     <div class="collections-detail-main">
-      <h3>Blue-and-white Snuff Bottle with Mythic Fungus Design</h3>
+      <h3>{{ detail?.name }}</h3>
 
-      <div></div>
+      <div v-for="item in rtf" :key="item.id" v-html="item.txt" />
     </div>
   </div>
 </template>
 
+<script setup lang="ts">
+import { getCollectionDetailApi, type CollectionDetail } from "@/api";
+import { getBaseURL } from "@dage/service";
+import { computed, onMounted, ref } from "vue";
+import { useRoute } from "vue-router";
+
+const baseUrl = getBaseURL();
+const route = useRoute();
+const loading = ref(false);
+const rtf = ref<{ id: number; txt: string }[]>([]);
+const detail = ref<CollectionDetail | null>(null);
+const imgUrl = computed(() =>
+  detail.value ? baseUrl + detail.value.thumb : ""
+);
+
+const getDetail = async () => {
+  try {
+    loading.value = true;
+    const data = await getCollectionDetailApi(Number(route.query.id));
+    detail.value = data;
+    rtf.value = JSON.parse(data.rtf).txtArr;
+  } finally {
+    loading.value = false;
+  }
+};
+
+onMounted(() => {
+  getDetail();
+});
+</script>
+
 <style lang="scss" scoped>
 @import "./index.scss";
 </style>

+ 10 - 5
src/views/Collections/List/index.vue

@@ -1,11 +1,12 @@
 <template>
   <div class="collections-list">
-    <PageBanner title="Ceramics" :img="bgImg" />
+    <PageBanner :title="route.params.name as string" :img="bgImg" />
 
     <VanList
       v-model:loading="loading"
       :finished="noMore"
       finished-text="no more"
+      :immediate-check="false"
       @load="onLoad"
     >
       <Waterfall
@@ -41,16 +42,22 @@ import { LazyImg, Waterfall } from "vue-waterfall-plugin-next";
 import { getBaseURL } from "@dage/service";
 import { getCollectionListApi, type CollectionListItem } from "@/api";
 import PageBanner from "@/components/PageBanner.vue";
+import { PaginationType, usePagination } from "@/utils/usePagination";
+import { useCollectionStore } from "@/stores/collection";
 import "vue-waterfall-plugin-next/dist/style.css";
 import { useRoute } from "vue-router";
-import { PaginationType, usePagination } from "@/utils/usePagination";
 
 const baseUrl = getBaseURL();
+const collectionStore = useCollectionStore();
 const route = useRoute();
 // 当前页图片数据是否全部加载完成
 const rendering = ref(false);
 const list = ref<any[]>([]);
-const bgImg = computed(() => decodeURIComponent(route.query.bg as string));
+const bgImg = computed(
+  () =>
+    baseUrl +
+    collectionStore.thumbList.find((i) => i.type === route.params.name)?.thumb
+);
 
 const {
   pageNum,
@@ -76,8 +83,6 @@ const loading = computed(() => rendering.value || fetchLoading.value);
 const onLoad = () => {
   // 检查用户是否滚动到页面底部
   if (!loading.value && !noMore.value) {
-    console.log("===");
-    console.log("Scrolled to bottom");
     pageNum.value++;
     getList();
   }

+ 17 - 20
src/views/Collections/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="collections">
-    <PageBanner title="Collections" :img="BannerImg" />
+    <PageBanner title="Collections" :img="bannerUrl || BannerImg" />
 
     <ul class="collections-main">
       <li
@@ -13,16 +13,14 @@
             params: {
               name: item.name,
             },
-            query: {
-              bg: encodeURIComponent(
-                baseUrl + list.find((i) => i.type === item.name)?.thumb
-              ),
-            },
           })
         "
       >
         <VanImage
-          :src="baseUrl + list.find((i) => i.type === item.name)?.thumb"
+          :src="
+            baseUrl +
+            collectionStore.thumbList.find((i) => i.type === item.name)?.thumb
+          "
           width="100%"
           :height="100"
           style="display: block"
@@ -34,14 +32,22 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref } from "vue";
 import PageBanner from "@/components/PageBanner.vue";
-import { getCollectionThumbListApi, type CollectionThumbListItem } from "@/api";
-import BannerImg from "./images/bannerC.png";
 import { getBaseURL } from "@dage/service";
+import { useCollectionStore } from "@/stores/collection";
+import BannerImg from "./images/bannerC.png";
+import { useBaseStore } from "@/stores/base";
+import { computed } from "vue";
 
 const baseUrl = getBaseURL();
-const list = ref<CollectionThumbListItem[]>([]);
+const baseStore = useBaseStore();
+const bannerUrl = computed(() => {
+  return (
+    baseUrl +
+    baseStore.bannerList.find((i) => i.name === "Collections")?.thumbApp
+  );
+});
+const collectionStore = useCollectionStore();
 const NAV_LIST = [
   {
     name: "Bronzes",
@@ -58,15 +64,6 @@ const NAV_LIST = [
   { name: "Cultural Supplies", type: "Cultural" },
   { name: "Miscellaneous", type: "Miscellaneous" },
 ];
-
-onMounted(() => {
-  getThumbList();
-});
-
-const getThumbList = async () => {
-  const data = await getCollectionThumbListApi();
-  list.value = data;
-};
 </script>
 
 <style lang="scss" scoped>

+ 15 - 0
src/views/Events/Detail/index.scss

@@ -30,6 +30,10 @@
       color: #6a6a6a;
       line-height: 48px;
 
+      &.active {
+        color: var(--van-primary-color);
+        font-weight: bold;
+      }
       span {
         position: absolute;
         left: 0;
@@ -37,4 +41,15 @@
       }
     }
   }
+
+  &-container {
+    padding: 40px 30px 20px;
+
+    :deep(p) {
+      margin-bottom: 30px;
+      font-size: 32px;
+      line-height: 36px;
+      color: #6a6a6a;
+    }
+  }
 }

+ 67 - 11
src/views/Events/Detail/index.vue

@@ -1,30 +1,86 @@
 <template>
   <div class="events-detail">
-    <PageBanner title="Events" :img="BannerImg" />
+    <PageBanner title="Events" :img="detail ? baseUrl + detail.thumb : ''" />
 
     <div class="events-detail-main">
       <div class="events-detail-top">
-        <div class="page-title">International Museum Day 2021</div>
-        <p class="events-detail-top__date">May 18, 2021</p>
+        <div class="page-title">{{ detail?.name }}</div>
+        <p v-if="detail" class="events-detail-top__date">
+          {{ getFormatDate(detail.dateStart, detail.dateEnd) }}
+        </p>
       </div>
 
       <div class="events-detail-directory">
-        <p>
-          <span>■</span> Forum "The Future of Museums: New Journey and New
-          Actions"
-        </p>
-        <p>
-          <span>■</span> Museum Youth Forum "The Future of Museums: The Mission
-          and Responsibility of Young Museum Professionals"
+        <p
+          v-for="(title, idx) in detail?.titleArr"
+          :key="title"
+          :class="{
+            active: idx === tabIndex,
+          }"
+          @click="
+            () => {
+              tabIndex = idx;
+              $router.replace({
+                name: 'EventsDetail',
+                params: {
+                  id: idx === 0 ? detail?.id : `${detail?.id}.${idx}`,
+                },
+              });
+            }
+          "
+        >
+          <span>■</span> {{ title }}
         </p>
       </div>
+
+      <div
+        class="events-detail-container"
+        v-html="detail?.txtArr[tabIndex].txt"
+      />
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
 import PageBanner from "@/components/PageBanner.vue";
-import BannerImg from "../images/banner.png";
+import { getBaseURL } from "@dage/service";
+import { useRoute } from "vue-router";
+import { onMounted, ref } from "vue";
+import { getFormatDate } from "@/utils/date";
+import { getEventDetailApi, type EventDetail, type TxtArrItem } from "@/api";
+
+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);
+
+onMounted(() => {
+  getDetail();
+});
+
+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;
+  }
+};
 </script>
 
 <style lang="scss" scoped>

+ 3 - 1
src/views/Events/index.scss

@@ -12,8 +12,9 @@
     overflow: hidden;
     height: 422px;
     color: white;
-    background: red;
     border-radius: 16px;
+    background-size: cover;
+    background-position: center;
 
     h3 {
       position: relative;
@@ -21,6 +22,7 @@
       padding-bottom: 16px;
       font-weight: bold;
       font-size: 32px;
+      text-align: center;
 
       &::after {
         content: "";

+ 55 - 6
src/views/Events/index.vue

@@ -2,21 +2,70 @@
   <div class="events">
     <PageBanner title="Events" :img="BannerImg" />
 
-    <div
+    <VanList
+      v-model:loading="loading"
       class="events-list"
-      @click="$router.push({ name: 'EventsDetail', params: { id: 2 } })"
+      :finished="noMore"
+      finished-text="no more"
+      :immediate-check="false"
+      @load="onLoad"
     >
-      <div class="events-item">
-        <h3>International Museum Day 2021</h3>
-        <i>Date:May 18, 2021</i>
+      <div
+        v-for="item in list"
+        :key="item.id"
+        class="events-item"
+        :style="{
+          backgroundImage: `url(${baseUrl + item.thumb})`,
+        }"
+        @click="$router.push({ name: 'EventsDetail', params: { id: item.id } })"
+      >
+        <h3>{{ item.name }}</h3>
+        <i>{{ item.date }}</i>
       </div>
-    </div>
+    </VanList>
   </div>
 </template>
 
 <script setup lang="ts">
 import PageBanner from "@/components/PageBanner.vue";
 import BannerImg from "./images/banner.png";
+import { getBaseURL } from "@dage/service";
+import { usePagination } from "@/utils/usePagination";
+import { getEventListApi, type EventListItem } from "@/api";
+import { computed, onMounted } from "vue";
+import { getFormatDate } from "@/utils/date";
+
+const baseUrl = getBaseURL();
+const {
+  pageNum,
+  loading,
+  list: _list,
+  noMore,
+  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 onLoad = () => {
+  if (noMore.value) return;
+
+  pageNum.value++;
+  getList();
+};
+
+onMounted(() => {
+  getList();
+});
 </script>
 
 <style lang="scss" scoped>

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

@@ -6,7 +6,7 @@
       class="current-item"
       @click="$router.push({ name: 'ExDetail', query: { id: item.id } })"
     >
-      <VanImage :src="baseUrl + item.thumb" style="width: 100%" />
+      <VanImage lazy-load :src="baseUrl + item.thumb" style="width: 100%" />
       <h3>{{ item.name }}</h3>
       <p>
         {{ item.digest }}

TEMPAT SAMPAH
src/views/JoinSupport/images/bgD.png


+ 15 - 4
src/views/JoinSupport/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="join">
-    <PageBanner title="Join & Support" :img="BannerImg" />
+    <PageBanner title="Join & Support" :img="bannerUrl || BannerImg" />
 
     <PageNav
       v-if="$route.name !== 'JoinInfo'"
@@ -8,10 +8,10 @@
       @click-tab="handleNav"
     >
       <van-tab title="Ways to Volunteer">
-        <RouterView />
+        <RouterView v-if="route.name === 'JoinVo'" />
       </van-tab>
       <van-tab title="Ways to Give">
-        <RouterView />
+        <RouterView v-if="route.name === 'JoinGi'" />
       </van-tab>
     </PageNav>
 
@@ -20,11 +20,13 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, watch } from "vue";
+import { computed, ref, watch } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import PageBanner from "@/components/PageBanner.vue";
 import PageNav from "@/components/PageNav.vue";
 import BannerImg from "./images/bannerJ.png";
+import { getBaseURL } from "@dage/service";
+import { useBaseStore } from "@/stores/base";
 
 const ROUTE_MAP: Record<string, string> = {
   Volunteer: "JoinVo",
@@ -35,6 +37,15 @@ const route = useRoute();
 const router = useRouter();
 const activeTab = ref(0);
 
+const baseUrl = getBaseURL();
+const baseStore = useBaseStore();
+const bannerUrl = computed(() => {
+  return (
+    baseUrl +
+    baseStore.bannerList.find((i) => i.name === "Join&Support")?.thumbApp
+  );
+});
+
 watch(
   route,
   () => {

+ 2 - 4
src/views/Learn/Adults/index.vue

@@ -1,9 +1,7 @@
 <template>
-  <div class="learn-list">
-    <Card v-for="key in 5" :key="key" />
-  </div>
+  <List />
 </template>
 
 <script setup lang="ts">
-import Card from "../components/Card.vue";
+import List from "../components/List.vue";
 </script>

+ 12 - 0
src/views/Learn/Detail/index.scss

@@ -31,4 +31,16 @@
       background: url("@/assets/images/bg_7.png") 6px 16px no-repeat;
     }
   }
+  &-inner {
+    &__html {
+      margin-top: 30px;
+
+      :deep(p) {
+        margin-bottom: 30px;
+        font-size: 32px;
+        line-height: 36px;
+        color: #6a6a6a;
+      }
+    }
+  }
 }

+ 70 - 11
src/views/Learn/Detail/index.vue

@@ -5,25 +5,31 @@
     <div class="learn-detail-main">
       <div class="learn-detail-info">
         <h3>
-          Series of activities of the educational practice base for forging a
-          sense of Chinese national community - making a ‘sundial’
+          {{ detail?.name }}
         </h3>
 
         <p class="learn-detail-info__time">
-          Tuesday to Sunday (closed on Mondays) 9:00 a.m. to 5:00 p.m.
+          {{ date }}
+        </p>
+        <p v-if="detail?.address" class="learn-detail-info__address">
+          {{ detail?.address }}
+        </p>
+        <p v-if="detail?.remark" class="learn-detail-info__team">
+          {{ detail?.remark }}
         </p>
-        <p class="learn-detail-info__address">Room B, F1</p>
-        <p class="learn-detail-info__team">Limited to 20 people in total</p>
       </div>
 
       <div class="learn-detail-inner">
-        <VanImage
-          src="https://en.capitalmuseum.org.cn/data/LearnEngage/in/101.jpg"
-        />
+        <VanImage :src="baseUrl + detail?.thumb" />
 
-        <div class="learn-detail-inner__html"></div>
+        <div
+          class="learn-detail-inner__html"
+          v-for="item in rtf"
+          :key="item.id"
+          v-html="item.txt"
+        />
 
-        <div class="learn-detail-inner__ft">
+        <!-- <div class="learn-detail-inner__ft">
           <p>How to sign up</p>
           <p>
             Please scan the applet code below to make a reservation, or click on
@@ -32,7 +38,7 @@
             or telephone registration).
           </p>
           <img src="" />
-        </div>
+        </div> -->
       </div>
     </div>
   </div>
@@ -41,6 +47,59 @@
 <script lang="ts" setup>
 import PageBanner from "@/components/PageBanner.vue";
 import BannerImg from "../images/bannerLI.png";
+import { computed, onMounted, ref } from "vue";
+import { getLearnDetailApi, type LearnDetail } from "@/api";
+import { useRoute } from "vue-router";
+import { getBaseURL } from "@dage/service";
+
+const route = useRoute();
+const baseUrl = getBaseURL();
+
+const loading = ref(false);
+const detail = ref<null | LearnDetail>(null);
+const rtf = ref<{ id: number; txt: string }[]>([]);
+const date = computed(() => {
+  if (!detail.value) return;
+
+  let endDate: Date | null = null;
+  const date = new Date(detail.value.dateStart.replace(" ", "T"));
+
+  if (detail.value.dateEnd) {
+    endDate = new Date(detail.value.dateEnd.replace(" ", "T"));
+  }
+
+  const options: Intl.DateTimeFormatOptions = {
+    year: "numeric",
+    month: "long",
+    day: "numeric",
+    hour: "numeric",
+    minute: "numeric",
+    hour12: true,
+  };
+
+  return endDate
+    ? `${date.toLocaleString("en-US", options)} - ${endDate.toLocaleString(
+        "en-US",
+        options
+      )}`
+    : date.toLocaleString("en-US", options);
+});
+
+onMounted(() => {
+  getDetail();
+});
+
+const getDetail = async () => {
+  try {
+    loading.value = true;
+
+    const data = await getLearnDetailApi(route.query.id as string);
+    detail.value = data;
+    rtf.value = JSON.parse(data.rtf).txtArr;
+  } finally {
+    loading.value = false;
+  }
+};
 </script>
 
 <style lang="scss" scoped>

+ 2 - 4
src/views/Learn/Families/index.vue

@@ -1,9 +1,7 @@
 <template>
-  <div class="learn-list">
-    <Card v-for="key in 1" :key="key" />
-  </div>
+  <List />
 </template>
 
 <script setup lang="ts">
-import Card from "../components/Card.vue";
+import List from "../components/List.vue";
 </script>

+ 2 - 4
src/views/Learn/Students/index.vue

@@ -1,9 +1,7 @@
 <template>
-  <div class="learn-list">
-    <Card v-for="key in 5" :key="key" />
-  </div>
+  <List />
 </template>
 
 <script setup lang="ts">
-import Card from "../components/Card.vue";
+import List from "../components/List.vue";
 </script>

+ 41 - 8
src/views/Learn/components/Card.vue

@@ -1,23 +1,56 @@
 <template>
   <div
     class="learn-card"
-    @click="$router.push({ name: 'LearnInfo', query: { id: 1 } })"
+    @click="$router.push({ name: 'LearnInfo', query: { id: item.id } })"
   >
-    <VanImage
-      src="https://en.capitalmuseum.org.cn/data/LearnEngage/sm/100.png"
-      style="display: block"
-    />
+    <VanImage lazy-load :src="baseUrl + item.thumb" style="display: block" />
 
     <div class="learn-card__inner">
       <h3>
-        Celebration of Spring--taking advantage of the east wind to fly paper
-        kites
+        {{ item.name }}
       </h3>
-      <p>Date:April 5, 2023 10:00 am</p>
+      <p>Date:{{ date }}</p>
     </div>
   </div>
 </template>
 
+<script setup lang="ts">
+import type { LearnPageItem } from "@/api";
+import { getBaseURL } from "@dage/service";
+import { computed } from "vue";
+
+const props = defineProps<{
+  item: LearnPageItem;
+}>();
+
+const baseUrl = getBaseURL();
+
+const date = computed(() => {
+  let endDate: Date | null = null;
+  const date = new Date(props.item.dateStart.replace(" ", "T"));
+
+  if (props.item.dateEnd) {
+    endDate = new Date(props.item.dateEnd.replace(" ", "T"));
+  }
+
+  const options: Intl.DateTimeFormatOptions = {
+    year: "numeric",
+    month: "long",
+    day: "numeric",
+    hour: "numeric",
+    minute: "numeric",
+    hour12: true,
+  };
+
+  return endDate
+    ? `${date.toLocaleString("en-US", options)} - ${endDate.toLocaleString(
+        "en-US",
+        options
+      )}`
+    : date.toLocaleString("en-US", options);
+});
+</script>
+
 <style lang="scss" scoped>
 .learn-card {
   background: white;

+ 60 - 0
src/views/Learn/components/List.vue

@@ -0,0 +1,60 @@
+<template>
+  <VanList
+    class="learn-list"
+    v-model:loading="loading"
+    :finished="noMore"
+    finished-text="no more"
+    :immediate-check="false"
+    @load="onLoad"
+  >
+    <Card v-for="item in list" :key="item.id" :item="item" />
+  </VanList>
+</template>
+
+<script setup lang="ts">
+import { onMounted } from "vue";
+import { useRoute } from "vue-router";
+import { getLearnPageListApi, type LearnPageItem } from "@/api";
+import { PaginationType, usePagination } from "@/utils/usePagination";
+import Card from "./Card.vue";
+
+const NAV_LIST = [
+  {
+    type: "LearnStudents",
+    name: "For Students",
+  },
+  {
+    type: "LearnAdults",
+    name: "For Adults",
+  },
+  {
+    type: "LearnFamilies",
+    name: "For Families & Children",
+  },
+];
+const route = useRoute();
+
+const { pageNum, loading, list, noMore, getList } =
+  usePagination<LearnPageItem>(
+    (params) => {
+      return getLearnPageListApi({
+        type: NAV_LIST.find((i) => i.type === route.name)?.name,
+        ...params,
+      });
+    },
+    PaginationType.CONCAT,
+    10
+  );
+
+onMounted(() => {
+  getList();
+});
+
+const onLoad = () => {
+  // 检查用户是否滚动到页面底部
+  if (!noMore.value) {
+    pageNum.value++;
+    getList();
+  }
+};
+</script>

TEMPAT SAMPAH
src/views/Learn/images/bgLI.png


+ 15 - 5
src/views/Learn/index.vue

@@ -1,27 +1,29 @@
 <template>
   <div class="learn">
-    <PageBanner title="" :img="BannerImg" />
+    <PageBanner title="" :img="bannerUrl || BannerImg" />
 
     <PageNav v-model="activeTab" @click-tab="handleNav">
       <van-tab title="Students">
-        <RouterView />
+        <RouterView v-if="route.name === ROUTE_MAP.Students" />
       </van-tab>
       <van-tab title="Adults">
-        <RouterView />
+        <RouterView v-if="route.name === ROUTE_MAP.Adults" />
       </van-tab>
       <van-tab title="Families & Children">
-        <RouterView />
+        <RouterView v-if="route.name === ROUTE_MAP.Families" />
       </van-tab>
     </PageNav>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { ref, watch } from "vue";
+import { computed, ref, watch } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import PageBanner from "@/components/PageBanner.vue";
 import PageNav from "@/components/PageNav.vue";
 import BannerImg from "./images/bannerL.png";
+import { getBaseURL } from "@dage/service";
+import { useBaseStore } from "@/stores/base";
 
 const ROUTE_MAP: Record<string, string> = {
   Students: "LearnStudents",
@@ -32,6 +34,14 @@ const ROUTE_MAP: Record<string, string> = {
 const route = useRoute();
 const router = useRouter();
 const activeTab = ref(0);
+const baseUrl = getBaseURL();
+const baseStore = useBaseStore();
+const bannerUrl = computed(() => {
+  return (
+    baseUrl +
+    baseStore.bannerList.find((i) => i.name === "Learn&Engage")?.thumbApp
+  );
+});
 
 watch(
   route,

+ 14 - 0
src/views/Publications/Catalogues/index.scss

@@ -0,0 +1,14 @@
+.catalogues {
+  padding: 20px 40px 40px;
+  background: #f7f6f3;
+
+  ul {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 30px;
+
+    li {
+      flex: 0 0 calc(50% - 15px);
+    }
+  }
+}

+ 60 - 1
src/views/Publications/Catalogues/index.vue

@@ -1,3 +1,62 @@
 <template>
-  <div class="catalogues"></div>
+  <div class="catalogues">
+    <VanList
+      v-model:loading="loading"
+      :finished="noMore"
+      finished-text="no more"
+      :immediate-check="false"
+      @load="onLoad"
+    >
+      <ul>
+        <li
+          v-for="item in list"
+          :key="item.id"
+          @click="
+            $router.push({
+              name: 'Pdf',
+              query: {
+                url: encodeURIComponent(baseUrl + item.filePath),
+                title: item.name,
+              },
+            })
+          "
+        >
+          <VanImage lazy-load :src="baseUrl + item.thumb" />
+        </li>
+      </ul>
+    </VanList>
+  </div>
 </template>
+
+<script setup lang="ts">
+import { getPublishListApi, type ExhibitionCatalogue } from "@/api";
+import { PaginationType, usePagination } from "@/utils/usePagination";
+import { getBaseURL } from "@dage/service";
+import { onMounted } from "vue";
+
+const baseUrl = getBaseURL();
+
+const { pageNum, list, noMore, loading, getList } =
+  usePagination<ExhibitionCatalogue>((params) => {
+    return getPublishListApi({
+      ...params,
+      type: "Exhibition",
+    });
+  }, PaginationType.CONCAT);
+
+onMounted(() => {
+  getList();
+});
+
+const onLoad = () => {
+  // 检查用户是否滚动到页面底部
+  if (!noMore.value) {
+    pageNum.value++;
+    getList();
+  }
+};
+</script>
+
+<style lang="scss" scope>
+@import "./index.scss";
+</style>

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

@@ -0,0 +1,30 @@
+.publication-detail {
+  &-top {
+    position: relative;
+
+    p {
+      font-size: 36px;
+      font-weight: bold;
+      color: #fff;
+      position: absolute;
+      bottom: 0;
+      left: 0;
+      width: 100%;
+      text-align: center;
+      padding: 30px 40px;
+      background-image: linear-gradient(transparent, #000);
+    }
+  }
+
+  &-inner {
+    padding: 60px 40px;
+    background: url("../images/bgPuD.png") repeat top / 100%;
+
+    :deep(p) {
+      margin-bottom: 30px;
+      font-size: 32px;
+      line-height: 36px;
+      color: #6a6a6a;
+    }
+  }
+}

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

@@ -0,0 +1,48 @@
+<template>
+  <div class="publication-detail">
+    <div class="publication-detail-top">
+      <VanImage
+        :src="baseUrl + detail?.thumb"
+        :alt="detail?.name"
+        width="100%"
+      />
+      <p>{{ detail?.name }}</p>
+    </div>
+
+    <div
+      v-for="item in rtf"
+      :key="item.id"
+      v-html="item.txt"
+      class="publication-detail-inner"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { getPublishDetailApi, type PublicationDetail } from "@/api";
+import { getBaseURL } from "@dage/service";
+import { onMounted, ref } from "vue";
+import { useRoute } from "vue-router";
+
+const route = useRoute();
+const baseUrl = getBaseURL();
+const detail = ref<PublicationDetail | null>(null);
+const rtf = ref<{ id: number; txt: string }[]>([]);
+
+onMounted(() => {
+  getDetail();
+});
+
+const getDetail = async () => {
+  try {
+    const data = await getPublishDetailApi(route.query.id as string);
+    detail.value = data;
+    rtf.value = JSON.parse(data.rtf).txtArr;
+  } finally {
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 68 - 15
src/views/Publications/Magazines/components/TimeLinePanel.vue

@@ -6,7 +6,7 @@
         :key="year"
         class="time-line-panel-year__item"
         :class="{ active: activeYear === year }"
-        @click="activeYear = year"
+        @click="handleYear(year)"
       >
         <p>{{ year }}</p>
       </div>
@@ -15,28 +15,38 @@
     <!-- 书籍卡片 -->
     <div class="card" :style="{ opacity }">
       <div
-        v-for="(item, index) in imgList"
+        v-for="(item, index) in list"
         :key="item.id"
         class="row"
         :style="`left:${index * 15}px;height:${100 - index * 5}%;opacity:${
           1 - index * 0.2 <= 0 ? 0.1 : 1 - index * 0.2
-        }; z-index: ${imgList.length - index};`"
+        }; z-index: ${list.length - index};`"
       >
-        <div
+        <VanImage
           v-if="index === 0"
-          tag="img"
           v-touch:swipe.left="moveSwiper"
           v-touch:swipe.right="moveSwiper"
-          :src="info.imgUrl"
-          alt=""
+          fit="fill"
+          :src="baseUrl + info.thumb"
+          :alt="info.name"
+          @click="
+            $router.push({ name: 'PublicationDetail', query: { id: info.id } })
+          "
         />
       </div>
+
+      <comp-loading v-if="loading" />
+
+      <VanEmpty v-if="noData" image="search" description="No data" />
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { ref } from "vue";
+import { getPublishListApi, type PubicationItem } from "@/api";
+import { getBaseURL } from "@dage/service";
+import { debounce } from "lodash-unified";
+import { computed, onMounted, ref } from "vue";
 
 const MIN_YEAR = 2017;
 const date = new Date();
@@ -46,25 +56,66 @@ const years = Array.from(
   (_, index) => year - index
 );
 
+const baseUrl = getBaseURL();
 const activeYear = ref(years[0]);
-const imgList = ref<any[]>([]);
 const infoInd = ref(0);
 const info = ref<any>({});
 const opacity = ref(1);
 
+const PAGE_SIZE = 8;
+const pageNum = ref(1);
+const total = ref(0);
+const noData = computed(() => !total.value);
+const loading = ref(false);
+const list = ref<PubicationItem[]>([]);
+
+onMounted(() => {
+  getList();
+});
+
+const debounceSearch = debounce(() => {
+  pageNum.value = 1;
+  getList();
+}, 500);
+
+const getList = async () => {
+  try {
+    loading.value = true;
+    const data = await getPublishListApi({
+      pageSize: PAGE_SIZE,
+      pageNum: pageNum.value,
+      year: activeYear.value,
+      type: "Magazines",
+    });
+    total.value = data.total;
+    list.value = data.records;
+
+    if (data.records.length) {
+      info.value = data.records[0];
+    }
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleYear = (year: number) => {
+  activeYear.value = year;
+  debounceSearch();
+};
+
 const moveSwiper = (type: "left" | "right") => {
   opacity.value = 0;
   setTimeout(() => {
     if (type === "right") {
       // 右滑减小
-      if (infoInd.value === 0) infoInd.value = imgList.value.length - 1;
+      if (infoInd.value === 0) infoInd.value = list.value.length - 1;
       else infoInd.value--;
     } else {
       //左滑增加
-      if (infoInd.value < imgList.value.length - 1) infoInd.value++;
+      if (infoInd.value < list.value.length - 1) infoInd.value++;
       else infoInd.value = 0;
     }
-    info.value = imgList.value[infoInd.value];
+    info.value = list.value[infoInd.value];
     opacity.value = 1;
   }, 300);
 };
@@ -133,16 +184,18 @@ const moveSwiper = (type: "left" | "right") => {
     transition: all 0.3s;
 
     .row {
-      border-radius: 16px;
       position: absolute;
       left: 0;
       top: 50%;
       transform: translateY(-50%);
       width: 80%;
       height: 800px;
-      background: url("../images/PuBc.jpg");
+      overflow: hidden;
+      border-radius: 16px;
+      background: url("../../images/PuBc.jpg");
       background-size: 100% 100%;
-      & > img {
+
+      .van-image {
         touch-action: pan-y !important;
         border-radius: 16px;
         width: 100%;

TEMPAT SAMPAH
src/views/Publications/images/bgPu.png


TEMPAT SAMPAH
src/views/Publications/images/bgPuD.png


+ 15 - 4
src/views/Publications/index.vue

@@ -1,24 +1,26 @@
 <template>
   <div class="publications">
-    <PageBanner title="Publications" :img="BannerImg" />
+    <PageBanner title="Publications" :img="bannerUrl || BannerImg" />
 
     <PageNav v-model="activeTab" @click-tab="handleNav">
       <van-tab title="Magazines">
-        <RouterView />
+        <RouterView v-if="route.name === ROUTE_MAP.Magazines" />
       </van-tab>
       <van-tab title="Catalogues">
-        <RouterView />
+        <RouterView v-if="route.name === ROUTE_MAP.Catalogues" />
       </van-tab>
     </PageNav>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { ref, watch } from "vue";
+import { computed, ref, watch } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import PageBanner from "@/components/PageBanner.vue";
 import PageNav from "@/components/PageNav.vue";
 import BannerImg from "./images/bannerP.png";
+import { getBaseURL } from "@dage/service";
+import { useBaseStore } from "@/stores/base";
 
 const ROUTE_MAP: Record<string, string> = {
   Magazines: "PuMagazines",
@@ -29,6 +31,15 @@ const route = useRoute();
 const router = useRouter();
 const activeTab = ref(0);
 
+const baseUrl = getBaseURL();
+const baseStore = useBaseStore();
+const bannerUrl = computed(() => {
+  return (
+    baseUrl +
+    baseStore.bannerList.find((i) => i.name === "Publications")?.thumbApp
+  );
+});
+
 watch(
   route,
   () => {

+ 36 - 0
src/views/Publications/pdf.vue

@@ -0,0 +1,36 @@
+<template>
+  <VanNavBar
+    :title="(route.query.title as string) || ''"
+    left-arrow
+    @click-left="onClickLeft"
+  />
+
+  <div id="pdf" style="height: calc(100vh - var(--van-nav-bar-height))" />
+</template>
+
+<script setup lang="ts">
+// @ts-ignore
+import Pdfh5 from "pdfh5";
+import "pdfh5/css/pdfh5.css";
+import { onMounted } from "vue";
+import { useRoute, useRouter } from "vue-router";
+
+const route = useRoute();
+const router = useRouter();
+
+onMounted(() => {
+  new Pdfh5("#pdf", {
+    pdfurl: decodeURIComponent(route.query.url as string),
+    goto: 1,
+    lazy: true,
+  });
+});
+
+const onClickLeft = () => {
+  if (window.history.length > 1) {
+    router.back();
+  } else {
+    router.replace({ name: "layout" });
+  }
+};
+</script>

TEMPAT SAMPAH
src/views/Search/images/bannerRes.png


+ 47 - 0
src/views/Search/index.scss

@@ -0,0 +1,47 @@
+.search {
+  &-main {
+    padding: 40px;
+  }
+  &-label {
+    margin-bottom: 30px;
+    font-size: 36px;
+    font-family: Bold;
+    padding-left: 60px;
+    background: url("@/assets/images/chosen.png") 0 no-repeat;
+    background-size: 44px 36px;
+
+    span {
+      padding-right: 20px;
+      font-weight: bold;
+      color: var(--van-primary-color);
+    }
+  }
+
+  ul {
+    display: flex;
+    flex-direction: column;
+    gap: 20px;
+  }
+
+  &-item {
+    display: flex;
+    box-shadow: 0 2px 4px 4px #ccc;
+
+    .van-image {
+      margin-right: 10px;
+      flex-shrink: 0;
+    }
+    &-inner {
+      flex: 1;
+      padding: 20px;
+    }
+    &__title {
+      margin-bottom: 10px;
+      font-size: 32px;
+      font-weight: bold;
+    }
+    &__content {
+      color: #6a6a6a;
+    }
+  }
+}

+ 304 - 0
src/views/Search/index.vue

@@ -0,0 +1,304 @@
+<template>
+  <div class="search">
+    <PageBanner title="Result" :img="BannerImg" />
+
+    <PageNav v-model="activeTabbar">
+      <van-tab v-for="item in tabbar" :key="item.module" :title="item.name" />
+    </PageNav>
+
+    <div class="search-main">
+      <div class="search-label">
+        <p>
+          <span>{{ tabbar[activeTabbar].total }}</span
+          >results
+        </p>
+      </div>
+
+      <VanList
+        v-model:loading="loading"
+        :finished="noMore"
+        finished-text="no more"
+        :immediate-check="false"
+        @load="onLoad"
+      >
+        <ul>
+          <li
+            v-for="item in list"
+            :key="item.id"
+            class="search-item"
+            @click="handleClick(item)"
+          >
+            <VanImage
+              v-if="item.thumb"
+              lazy-load
+              :width="120"
+              :height="120"
+              :src="item.thumb"
+            />
+
+            <div class="search-item-inner">
+              <p class="search-item__title limit-line">
+                {{ item.name }}
+              </p>
+              <p class="search-item__content limit-line line-4">
+                {{ item.rtf }}
+              </p>
+            </div>
+          </li>
+        </ul>
+      </VanList>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, ref, watch } from "vue";
+import PageBanner from "@/components/PageBanner.vue";
+import PageNav from "@/components/PageNav.vue";
+import BannerImg from "./images/bannerRes.png";
+import { useRoute, useRouter } from "vue-router";
+import { cloneDeep, isUndefined } from "lodash-unified";
+import { JoinSupport, About, TermsOfUse, Employment, Visit } from "@/data";
+import { searchApi, type SearchItem } from "@/api";
+import { getBaseURL } from "@dage/service";
+
+const _VISIT = cloneDeep(Visit);
+Object.values(_VISIT.Reservation.children).forEach((rtf) => {
+  _VISIT.Reservation.rtf += rtf;
+});
+
+const PAGE_SIZE = 20;
+const FRONT_DATA = [
+  ...Object.values(JoinSupport).flat(),
+  ...Object.values(_VISIT).flat(),
+  About.Director,
+  TermsOfUse,
+  Employment,
+];
+
+const baseUrl = getBaseURL();
+const router = useRouter();
+const route = useRoute();
+const activeTabbar = ref(
+  isUndefined(route.query.activeTabbar) ? 0 : Number(route.query.activeTabbar)
+);
+const tabbar = ref([
+  { name: "All Results", module: "", total: 0 },
+  { 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 getAbstract = (str: string) => {
+  const txt = str.replace(/<[^>]*>/g, " ");
+  return `${txt.slice(0, 200)}${txt.length > 200 ? "..." : ""}`;
+};
+
+const loading = ref(false);
+const pageNum = ref(1);
+const noMore = ref(false);
+const list = ref<SearchItem[]>([]);
+
+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[1].total += 1;
+          break;
+        case "join":
+          cloneTabbar[6].total += 1;
+          break;
+        case "about":
+          cloneTabbar[7].total += 1;
+          break;
+        case "terms":
+          cloneTabbar[9].total += 1;
+          break;
+        case "employment":
+          cloneTabbar[10].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),
+    }));
+
+    // 需要请求接口的tab索引
+    const BACKEND_MODULE_INDEX = [0, 2, 3, 4, 5, 8];
+    const data = await searchApi({
+      pageNum: pageNum.value,
+      pageSize: PAGE_SIZE,
+      searchKey: k,
+      // @ts-ignore
+      module:
+        activeTabbar.value === 0 ||
+        !BACKEND_MODULE_INDEX.includes(activeTabbar.value)
+          ? undefined
+          : tabbar.value[activeTabbar.value].module,
+    });
+
+    cloneTabbar[0].total = 0;
+    Object.keys(data.count).forEach((key) => {
+      const target = cloneTabbar.find((i) => i.module === key);
+      if (target) target.total = data.count[key];
+      cloneTabbar[0].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),
+                  ""
+                )
+              )
+            : "",
+        }))
+      );
+    }
+
+    tabbar.value = cloneTabbar;
+    list.value = stack;
+
+    noMore.value = stack.length < PAGE_SIZE;
+  } finally {
+    loading.value = false;
+  }
+};
+
+const onLoad = () => {
+  if (noMore.value) return;
+
+  pageNum.value++;
+  getList();
+};
+
+const VISIT_MAP: Record<string, Function> = {
+  reservation: () => {
+    router.push({ name: "VisitInfo" });
+  },
+  guide: () => {
+    router.push({ name: "Visit", params: { id: 4 } });
+  },
+  accessibility: () => {
+    router.push({ name: "Visit", params: { id: 5 } });
+  },
+  shop: () => {
+    router.push({ name: "Visit", params: { id: 6 } });
+  },
+};
+const handleClick = (item: SearchItem) => {
+  switch (item.module as string) {
+    case "visit":
+      VISIT_MAP[item.type]();
+      break;
+    case "learn":
+      router.push({ name: "LearnInfo", query: { id: item.id } });
+      break;
+    case "exhibition":
+      router.push({ name: "ExDetail", query: { id: item.id } });
+      break;
+    case "event":
+      router.push({ name: "EventsDetail", params: { id: item.id } });
+      break;
+    case "collection":
+      router.push({ name: "CollectionsDetail", query: { id: item.id } });
+      break;
+    case "publish":
+      if (item.type === "Magazines") {
+        router.push({
+          name: "PublicationDetail",
+          query: { id: item.id },
+        });
+      } else {
+        router.push({
+          name: "Pdf",
+          query: {
+            url: encodeURIComponent(baseUrl + item.filePath),
+            title: item.name,
+          },
+        });
+      }
+      break;
+    case "join":
+      if (item.type === "volunteer") {
+        router.push({ name: "JoinInfo", query: { id: item.id } });
+      } else {
+        router.push({ name: "JoinGi" });
+      }
+      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;
+  }
+};
+
+watch(
+  route,
+  () => {
+    getList();
+  },
+  {
+    immediate: true,
+  }
+);
+
+watch(activeTabbar, () => {
+  router.replace({
+    name: "Search",
+    query: { ...route.query, activeTabbar: activeTabbar.value },
+  });
+});
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 5 - 58
src/views/VisitInfo/components/Guidelines/index.vue

@@ -1,60 +1,7 @@
 <template>
-  <div class="guidelines">
-    <div class="title">Ways of Reservation</div>
-    <img src="../../images/pp1.jpg" alt="" />
-    <p>
-      There are 3,600 daily personal booking places, available through website
-      (3000 places) and telephone (600 places). The website
-      <a href="#/" class="indexUrl"
-        >https://en.capitalmuseum.org.cn/#/Layout/Home</a
-      >
-      offers 24-hour service, and the telephone <br />+86 (10) 63393339 service
-      is available from 09:00 to 17:00. One person is only allowed to book one
-      ticket.
-    </p>
-    <p>
-      For group reservation, we offer 400 tickets a day. Please call +86 (10)
-      63370458 between 09:00 to 17:00. Identity information of the group leader
-      is required.
-    </p>
-    <p>
-      Reservation should be made at least one day in advance, and at most seven
-      days in advance.
-    </p>
-    <div class="title">Way to Get Ticket</div>
-    <img src="../../images/pp2.jpg" alt="" />
-    <p>
-      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.
-    </p>
-    <p>
-      For group visitors, the leader can obtain the ticket at the east door of
-      the ground floor with valid documents and introductory letters.
-    </p>
-    <div class="title">Entrance Time</div>
-    <img src="../../images/pp3.jpg" alt="" />
-    <p>From 09:00-16:00, Tuesday to Sunday.</p>
-    <p>The museum is closed every Monday, except for holidays.</p>
-    <p>Please enter the museum before 16:00.</p>
-    <div class="title">Special Notices</div>
-    <p>
-      1. One ticket is only for one person and the ticket is only valid on the
-      date printed. Please have the ticket checked at the entrance.
-    </p>
-    <p>
-      2. Senior citizens (above 60) and handicapped persons can enter the
-      exhibition with valid documents even without reservation. Please ask
-      museum personnel for help.
-    </p>
-    <p>
-      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>
-    <p>
-      4. The exhibition lasts for three months. Please keep this in mind when
-      planning a visit.
-    </p>
-  </div>
+  <div class="guidelines" v-html="Visit.Reservation.children.Guidelines" />
 </template>
+
+<script setup lang="ts">
+import { Visit } from "@/data";
+</script>

+ 5 - 10
src/views/VisitInfo/components/Information/index.vue

@@ -1,12 +1,7 @@
 <template>
-  <div class="information">
-    <p>
-      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 publicannouncement in advance. Please visit the official
-      website or inquire by telephone for detailed information.
-    </p>
-    <p>Official website: https://en.capitalmuseum.org.cn/#/Layout/Home</p>
-    <p>Phone: +86 (10) 63370491</p>
-  </div>
+  <div class="information" v-html="Visit.Reservation.children.Information" />
 </template>
+
+<script setup lang="ts">
+import { Visit } from "@/data";
+</script>

+ 5 - 14
src/views/VisitInfo/components/Visit/index.vue

@@ -1,16 +1,7 @@
 <template>
-  <div class="visit">
-    <img src="../../images/pp5.jpg" alt="" />
-
-    <p>
-      We encourage groups and travel agencies to make reservations by telephone.
-    </p>
-    <p>
-      Requirements: Name of the tour group, full name of the person making the
-      reservation,
-    </p>
-    <p>contact information and number of group members.</p>
-    <p>Telephone reservation (group visitors): +86 (10) 63370458</p>
-    <img src="../../images/pp6.jpg" alt="" />
-  </div>
+  <div class="visit" v-html="Visit.Reservation.children.Visit" />
 </template>
+
+<script setup lang="ts">
+import { Visit } from "@/data";
+</script>