|
@@ -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>
|