Преглед на файлове

🚧 重建整理优化项目3 非遗详情页

快乐的梦鱼 преди 1 месец
родител
ревизия
a93084beb7

+ 3 - 2
src/assets/scss/main.scss

@@ -1,4 +1,5 @@
 html, body {
+  position: relative;
   margin: 0;
   padding: 0;
   font-size: 20px;
@@ -105,8 +106,8 @@ main {
     position: absolute;
     top: 0;
     left: 0;
-    width: 100%;
-    height: 100%;
+    right: 0;
+    bottom: 0;
     z-index: 10;
   }
 }

+ 5 - 0
src/router/index.ts

@@ -35,6 +35,11 @@ const router = createRouter({
       component: () => import('../views/Details/ArtifactDetail.vue'),
     },
     {
+      path: '/intangible',
+      name: 'IntangibleDetail',
+      component: () => import('../views/Details/IntangibleDetail.vue'),
+    },
+    {
       path: '/artifact',
       name: 'ArtifactDetail',
       component: () => import('../views/Details/ArtifactDetail.vue'),

+ 12 - 1
src/views/Content/TabCommonList.vue

@@ -1,5 +1,5 @@
 <template>
-  <SimplePageListContentLoader class="d-flex flex-col mt-3 flex-fill" :loader="loader">
+  <SimplePageListContentLoader class="common-list d-flex flex-col mt-3 flex-fill" :loader="loader">
     <Tab v-if="mainBodyColumnAsTabs" v-model="activeSubTab" :tabs="mainBodyColumnAsTabs.map(p => ({ label: p }))" autoSize />
     <GridList 
       class="flex-fill"
@@ -43,12 +43,23 @@ const props = defineProps({
     type: String,
     default: undefined,
   },
+  loader: {
+    type: Function as PropType<(page: number, size: number) => Promise<{
+        list: GetContentListItem[];
+        total: number;
+      }>
+    >,
+    default: undefined,
+  }
 })
 
 const emit = defineEmits(['itemClick']);
 const activeSubTab = ref(0);
 const router = useRouter();
 const loader = useSimplePagerDataLoader(16, async (page, size) => {
+  if (props.loader)
+    return props.loader(page, size);
+
   let modelId = props.modelId as number;
   let mainBodyColumnId = props.mainBodyColumnId as number | 
     number[] | 

+ 18 - 4
src/views/Content/TabInherit.vue

@@ -54,9 +54,10 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue';
+import { ref, watch } from 'vue';
 import { useSimpleDataLoader } from '@/composeable/SimpleDataLoader';
-import { useRouter } from 'vue-router';
+import { useRoute, useRouter } from 'vue-router';
+import IndexContent from '@/api/introduction/IndexContent';
 import CommonContent, { GetContentListParams } from '@/api/CommonContent';
 import SimplePageListContentLoader from '@/components/SimplePageListContentLoader.vue';
 import GridList from '@/components/small/GridList.vue';
@@ -68,7 +69,6 @@ import { use } from 'echarts/core';
 import { CanvasRenderer } from 'echarts/renderers';
 import { PieChart } from 'echarts/charts';
 import { TitleComponent, TooltipComponent } from 'echarts/components';
-import IndexContent from '@/api/introduction/IndexContent';
 
 use([
   TitleComponent,
@@ -79,6 +79,7 @@ use([
 
 const emit = defineEmits(['itemClick']);
 const router = useRouter();
+const route = useRoute();
 const loader = useSimpleDataLoader(async () => {
 
   const stats = await IndexContent.getStats();
@@ -96,7 +97,7 @@ const loader = useSimpleDataLoader(async () => {
   return CommonContent.getContentList(new GetContentListParams().setModelId(1), 1, 12)
 });
 
-const tab = ref(0);
+const tab = ref(parseInt(route.query.subTab as string || '0'));
 const chartOptionCity = ref({
   tooltip: {
     trigger: 'item',
@@ -176,30 +177,43 @@ const intangibleList = [
     image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/fy_1.png',
     title: '非遗项目',
     modelId: 2,
+    detailsPage: 'IntangibleDetail',
   },
   {
     image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/fy_2.png',
     title: '传承人',
     modelId: 7,
     mainBodyColumnId: 38,
+    detailsPage: 'IntangibleDetail',
   },
   {
     image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/fy_3.png',
     title: '非遗产品',
     modelId: 9,
+    detailsPage: 'ArticleDetail',
   },
   {
     image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/fy_4.png',
     title: '非遗活动',
     modelId: 18,
     mainBodyColumnId: 290,
+    detailsPage: 'ArticleDetail',
   }
 ];
 
+watch(tab, (newVal) => {
+  router.replace({ query: { 
+    ...route.query,
+    subTab: newVal 
+  } });
+})
+
+
 function handleIntangibleClick(item: any) {
   router.push({ name: 'IntangibleList', query: { 
     modelId: item.modelId,
     mainBodyColumnId: item.mainBodyColumnId,
+    detailPageName: item.detailsPage,
   } });
 }
 function handleItemClick(item: any) {

+ 6 - 0
src/views/ContentView.vue

@@ -69,6 +69,12 @@ const backgroundType = computed(() => {
 })
 
 
+watch(activeTab, (newVal) => {
+  router.replace({ query: { 
+    ...route.query,
+    tab: newVal 
+  } });
+})
 watch(() => activeTab.value, (newVal) => {
   if (newVal === 0)
     router.push({ name: 'Home' });

+ 46 - 13
src/views/Details/CommonDetail.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { ref } from 'vue';
+import { ref, watch } from 'vue';
 import { useRoute } from 'vue-router';
 import { useSimpleDataLoader } from '@/composeable/SimpleDataLoader';
 import { Vue3Marquee } from 'vue3-marquee';
@@ -10,36 +10,44 @@ import Header from '@/components/parts/Header.vue';
 import ImageLine from '@/components/small/ImageLine.vue';
 import ImagePreview from '@/components/small/ImagePreview.vue';
 
+const emit = defineEmits(['contentLoaded']);
+
 const route = useRoute();
 const loader = useSimpleDataLoader(async () => {
-  return (await CommonContent.getContentDetail(Number(route.query.id)));
+  const content = (await CommonContent.getContentDetail(Number(route.query.id)));
+  emit('contentLoaded', content);
+  return content;
 });
 
 const showPreview = ref(false);
 const activeItem = ref(0);
 
+watch(route, () => loader.loadData(undefined, true));
+
 </script>
 
 <template>
   <main class="main-content main-bg main-bg1">
     <Header show-back absolute />
     <SimplePageContentLoader :loader="loader">
-      <div class="content absolute">
+      <div v-if="loader.content.value" class="content absolute">
         <div class="left" :style="{ backgroundImage: `url(${loader.content.value?.image})` }" />
         <div class="right">
           <h1>{{ loader.content.value?.title }}</h1>
           <slot name="afterTitle" :content="loader.content.value" />
-          <Vue3Marquee style="width:100%;" :duration="70" vertical>      
-            <div class="d-flex flex-col">
-              <slot name="beforeContent" :content="loader.content.value" />
-              <SimpleRichHtml :contents="[ loader.content.value?.content as string || '暂无' ]" noScroll />
-              <slot name="afterContent" :content="loader.content.value" />
-            </div>
-          </Vue3Marquee>
+          <slot name="content" :content="loader.content.value">
+            <Vue3Marquee style="width:100%;" :duration="70" :delay="10" vertical>      
+              <div class="d-flex flex-col">
+                <slot name="beforeContent" :content="loader.content.value" />
+                <SimpleRichHtml :contents="[ loader.content.value?.content as string || '暂无' ]" noScroll />
+                <slot name="afterContent" :content="loader.content.value" />
+              </div>
+            </Vue3Marquee>
+          </slot>
           <ImageLine 
             :data="loader.content.value?.images"
             :max-count="5" 
-            image-height="20vh"
+            image-height="10vh"
             @itemClick="(v, i) => { showPreview = true; activeItem = i }"
             @moreClick="showPreview = true; activeItem=5"
           />
@@ -75,7 +83,7 @@ const activeItem = ref(0);
   background: linear-gradient(90deg, rgba(240, 235, 222, 0.1) 0%, #f0ebde 15%, #f0ebde 100%);
   font-size: 0.8rem;
   color: var(--color-text);
-  padding: 8%;
+  padding: 6% 8%;
   padding-left: 20%;
   display: flex;
   flex-direction: column;
@@ -88,9 +96,34 @@ const activeItem = ref(0);
   :deep(.image-line) { 
     margin-top: 4%;
   }
+  :deep(.tab-container) { 
+    margin-top: 2%;
+  }
+  :deep(.vue-scroll-rect) {
+    flex: 1 1 0%;
+    min-height: 0;
+  }
+  :deep(.common-list) {
+    flex: 1 1 0%;
+    min-height: 0;
+  }
+  :deep(video) { 
+    width: 100%;
+    height: 50%;
+  }
 }
 
-@media (max-height: 600px) {
+@media (max-height: 800px) {
+  .right {
+    padding-top: 4%;
+    padding-bottom: 4%;
+  }
+}
+@media (max-height: 400px) {
+  .right {
+    padding-top: 2%;
+    padding-bottom: 2%;
+  }
 }
 
 </style>

+ 148 - 0
src/views/Details/IntangibleDetail.vue

@@ -0,0 +1,148 @@
+<script setup lang="ts">
+import Tab from '@/components/small/Tab.vue';
+import CommonDetail from './CommonDetail.vue';
+import { computed, ref, watch } from 'vue';
+import type { GetContentDetailItem } from '@/api/CommonContent';
+import SimpleRichHtml from '@/components/SimpleRichHtml.vue';
+import { ScrollRect } from '@imengyu/vue-scroll-rect';
+import TabCommonList from '../Content/TabCommonList.vue';
+import { useRoute } from 'vue-router';
+
+const route = useRoute();
+const currentContent = ref<GetContentDetailItem>();
+const activeSubTab = ref(0);
+const activeSubTabId = computed(() => {
+  return tabs.value[activeSubTab.value]?.id || tabs.value[0]?.id || 0;
+});
+const tabs = computed(() => {
+  const res = [
+    { label: '简介', id: 0 }
+  ] as { label: string, id: number }[];
+
+  if (currentContent.value?.audio)
+    res.push({ label: '音频', id: 1 });
+  if (currentContent.value?.video)
+    res.push({ label: '视频', id: 2 });
+  
+  if (currentContent.value?.associationMeList && (currentContent.value?.associationMeList as any[]).length > 0)
+    res.push({ label: '相关非遗', id: 5 });
+  if (currentContent.value?.worksList && (currentContent.value?.worksList as any[]).length > 0)
+    res.push({ label: '非遗作品', id: 3 });
+  if (currentContent.value?.inheritorsList && (currentContent.value?.inheritorsList as any[]).length > 0)
+    res.push({ label: '非遗传承人', id: 4 });
+  
+  return res;
+});
+
+watch(route, () => {
+  activeSubTab.value = 0;
+});
+
+async function loadSubList(page: number, pageSize: number, content: any, subList: string) {
+  const list = content[subList] as any[] || [];
+  if (subList == 'associationMeList') {
+    list.forEach((p) => {
+      p.bottomTags = [
+        p.levelText,
+        p.ichTypeText,
+        p.batchText,
+      ];
+    })
+  } else if (subList == 'ichSitesList') {
+    list.forEach((p) => {
+      p.bottomTags = [
+        content.ichTypeText,
+      ];
+    })
+  } else if (subList == 'inheritorsList') {
+    list.forEach((p) => {
+      p.bottomTags = [
+        p.levelText,
+        p.nation,
+        content.ichTypeText,
+      ];
+    }) 
+  }
+  return {
+    page: page,
+    total: list.length,
+    list: list.slice((page - 1) * pageSize, page * pageSize)
+  };
+}
+
+</script>
+
+<template>
+  <CommonDetail @contentLoaded="currentContent = $event">
+    <template #afterTitle="{ content }">
+      <div class="desc">
+        <div v-if="content.address">地址: {{ content.address }}</div>
+        <div v-if="content.ichTypeText">项目类别: {{ content.ichTypeText }}</div>
+        <div v-if="content.levelText">项目级别: {{ content.levelText }}</div>
+        <div v-if="content.batchText">批次时间: {{ content.batchText }}</div>
+        <div v-if="content.regionText">所属区域: {{ content.regionText }}</div>
+        <div v-if="content.unit">保护单位: {{ content.unit }}</div>
+      </div>
+    </template>
+    <template #content="{ content }">
+      <Tab 
+        v-model="activeSubTab"
+        :tabs="tabs"
+        autoSize
+        itemWidth="100px"
+      />
+      <ScrollRect v-if="activeSubTabId === 0" scroll="vertical">
+        <SimpleRichHtml :contents="[ content.intro as string, content.content ]" />
+      </ScrollRect>
+      <video 
+        v-else-if="activeSubTabId === 1"
+        controls
+        :src="content.audio" 
+      />
+      <video 
+        v-else-if="activeSubTabId === 2"
+        controls
+        :src="content.video" 
+      />
+      <TabCommonList 
+        v-else-if="activeSubTabId === 3" 
+        :loader="(page, size) => loadSubList(page, size, content, 'worksList')"
+        detailPageNames="IntangibleDetail"
+      />
+      <TabCommonList 
+        v-else-if="activeSubTabId === 5" 
+        :loader="(page, size) => loadSubList(page, size, content, 'associationMeList')"
+        detailPageNames="IntangibleDetail"
+      />
+      <TabCommonList 
+        v-else-if="activeSubTabId === 4" 
+        :loader="(page, size) => loadSubList(page, size, content, 'inheritorsList')"
+        detailPageNames="IntangibleDetail"
+      />
+    </template>
+
+  </CommonDetail>
+</template>
+
+<style lang="scss" scoped>
+h3 {
+  font-size: 0.8rem;
+  color: var(--color-text-primary);
+  text-align: left;
+  width: 100%;
+  margin-top: 0.5rem;
+}
+.desc {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  justify-content: space-between;
+
+  div {
+    width: 48%;
+    color: var(--color-text-primary);
+    font-size: 0.8rem;
+  }
+
+}
+</style>

+ 4 - 4
src/views/Intangible/List.vue

@@ -41,10 +41,10 @@ const loader = useSimplePagerDataLoader(16, async (page, size) => {
   , page, size)
 });
 const detailPageName = computed(() => {
-  let detailPageNames = route.query.detailPageNames as string;
-  if (!detailPageNames)
-    detailPageNames = 'ArticleDetail';
-  return detailPageNames;
+  let detailPageName = route.query.detailPageName as string;
+  if (!detailPageName)
+    detailPageName = 'ArticleDetail';
+  return detailPageName;
 })
 
 function handleClick(item: GetContentListItem) {

+ 30 - 0
src/views/Intangible/Map.vue

@@ -0,0 +1,30 @@
+<script setup lang="ts">
+import Header from '@/components/parts/Header.vue';
+import { ref } from 'vue';
+
+const zoom = ref(12);
+const center = ref([121.59996, 31.197646]);
+let map: any = null;
+
+function handleInit(mapRef: any) {
+  map = mapRef;
+}
+
+</script>
+
+<template>
+  <main class="main-content main-bg">
+    <Header show-back absolute />
+    <el-amap
+      :center="center"
+      :zoom="zoom"
+      @init="handleInit"
+    />
+
+  </main>
+</template>
+
+<style lang="scss" scoped>
+
+
+</style>