TabDetailView.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. <template>
  2. <!-- 文物详情页 -->
  3. <div class="main-background">
  4. <div class="nav-placeholder"></div>
  5. <!-- SEO -->
  6. <Head>
  7. <Title>{{ loader.content.value?.title }}</Title>
  8. <Meta name="description" :content="loader.content.value?.desc" />
  9. </Head>
  10. <!-- 新闻 -->
  11. <section class="main-section main-background main-background-type0 small-h">
  12. <SimplePageContentLoader :loader="loader">
  13. <div v-if="loader.content.value" class="content news-detail">
  14. <div class="d-flex flex-row justify-content-start mb-3">
  15. <div class="back-button2" @click="back">
  16. <img src="@/assets/images/news/IconBack.png" />
  17. <span>返回列表</span>
  18. </div>
  19. </div>
  20. <div class="d-flex flex-row justify-content-start">
  21. <h1 :class="{ 'title-box': loader.content.value.titleBox }">{{ loader.content.value.title }}</h1>
  22. </div>
  23. <p class="d-flex flex-row justify-content-between small-info">
  24. <span>{{ loader.content.value.address }}</span>
  25. <span v-if="loader.content.value.regionText">区域:{{ loader.content.value.regionText }}</span>
  26. <span v-if="loader.content.value.from" >来源:{{ loader.content.value.from }}</span>
  27. </p>
  28. <!-- Tab -->
  29. <TagBar
  30. class="mb-3"
  31. :tags="crrentVisibleTabs.map((p, i) => ({ id: p.id, name: p.text })) || []"
  32. :margin="[30, 70]"
  33. v-model:selectedTag="currentTabId"
  34. />
  35. <!-- 基础信息 -->
  36. <div v-show="currentTabId==0">
  37. <SimpleRichHtml
  38. class="news-content"
  39. :contents="[
  40. loader.content.value.intro,
  41. loader.content.value.content,
  42. ]"
  43. >
  44. <template #prepend>
  45. <!-- 轮播 -->
  46. <Carousel
  47. :itemsToShow="1"
  48. wrapAround
  49. :autoPlay="5000"
  50. class="carousel float"
  51. >
  52. <Slide v-for="(image, key) in loader.content.value.images" :key="key">
  53. <img :src="image" />
  54. </Slide>
  55. <template #addons>
  56. <Navigation />
  57. <Pagination />
  58. </template>
  59. </Carousel>
  60. <!-- 额外内容 -->
  61. <slot name="extraInfo" :content="loader.content.value" />
  62. </template>
  63. </SimpleRichHtml>
  64. </div>
  65. <!-- 图片 -->
  66. <div v-show="currentTabId==1">
  67. <ImageGrid
  68. v-if="loader.content.value.images && loader.content.value.images.length > 0"
  69. :data="loader.content.value.images"
  70. imageHeight="300px"
  71. @itemClick="handleShowImage"
  72. >
  73. </ImageGrid>
  74. <a-empty v-else />
  75. </div>
  76. <!-- 音频 -->
  77. <div v-show="currentTabId==2">
  78. <video
  79. v-if="loader.content.value.video"
  80. class="news-video mt-3"
  81. controls
  82. :src="loader.content.value.video"
  83. />
  84. <video
  85. v-if="loader.content.value.audio"
  86. class="news-video mt-3"
  87. controls
  88. :src="loader.content.value.audio"
  89. />
  90. <a-empty v-if="!loader.content.value.video && !loader.content.value.audio" />
  91. </div>
  92. <!-- 视频 -->
  93. <div v-show="currentTabId==3">
  94. <video
  95. v-if="loader.content.value.video"
  96. class="news-video mt-3"
  97. controls
  98. :src="loader.content.value.video"
  99. />
  100. <video
  101. v-if="loader.content.value.audio"
  102. class="news-video mt-3"
  103. controls
  104. :src="loader.content.value.audio"
  105. />
  106. <a-empty v-if="!loader.content.value.video && !loader.content.value.audio" />
  107. </div>
  108. <!-- 其他 -->
  109. <slot name="extraTab" :currentTabId="currentTabId" :content="loader.content.value" />
  110. <ContentNode />
  111. <div class="row d-flex justify-content-center">
  112. <div class="back-button" @click="back">
  113. <img src="@/assets/images/news/IconBack.png" />
  114. <span>返回列表</span>
  115. </div>
  116. </div>
  117. </div>
  118. </SimplePageContentLoader>
  119. </section>
  120. <a-image
  121. :width="200"
  122. :style="{ display: 'none' }"
  123. :preview="{
  124. visible: imagePreviewVisible,
  125. onVisibleChange: (v: boolean) => imagePreviewVisible = v,
  126. }"
  127. :src="imagePreviewSrc"
  128. />
  129. </div>
  130. </template>
  131. <script setup lang="ts">
  132. import { Carousel, Slide, Pagination, Navigation } from 'vue3-carousel'
  133. import type { GetContentDetailItem } from '@/api/CommonContent';
  134. import SimplePageContentLoader from '@/components/content/SimplePageContentLoader.vue';
  135. import SimpleRichHtml from '@/components/display/SimpleRichHtml.vue';
  136. import { useSSrSimpleDataLoader } from '@/composeable/SimpleDataLoader';
  137. import { useRoute, useRouter } from 'vue-router';
  138. import ContentNode from '@/components/content/ContentNode.vue';
  139. import { computed, onMounted, ref, watch, type PropType } from 'vue';
  140. import TagBar from '@/components/content/TagBar.vue';
  141. import ImageGrid from '@/components/content/ImageGrid.vue';
  142. const props = defineProps({
  143. load: {
  144. type: Function as PropType<(id: number) => Promise<GetContentDetailItem>>,
  145. default: null,
  146. }
  147. })
  148. const route = useRoute();
  149. const router = useRouter();
  150. const imagePreviewVisible = ref(false);
  151. const imagePreviewSrc = ref('');
  152. function handleShowImage(url: string) {
  153. imagePreviewVisible.value = true;
  154. imagePreviewSrc.value = url;
  155. }
  156. const loader = await useSSrSimpleDataLoader('details' + route.query.id, async () => {
  157. if (!props.load)
  158. throw new Error("!props.load");
  159. return (await props.load(Number(route.query.id))).toJSON();
  160. }, false);
  161. watch(() => route.query.id, (v) => {
  162. if (!v)
  163. return;
  164. loader.loadData(undefined, true);
  165. })
  166. const crrentVisibleTabs = computed(() => contentProps.value.tabs.filter((item) => item.visible));
  167. const currentTabId = ref(0);
  168. const contentProps = computed(() => {
  169. return loader.content.value?.contentProps as {
  170. tabs: {
  171. id: number,
  172. text: string,
  173. visible: boolean,
  174. }[],
  175. } ?? {
  176. tabs: [],
  177. };
  178. })
  179. watch(route, () => {
  180. currentTabId.value = 0;
  181. });
  182. onMounted(() => {
  183. currentTabId.value = 0;
  184. })
  185. function back() {
  186. router.back();
  187. }
  188. </script>
  189. <style lang="scss">
  190. </style>