Selaa lähdekoodia

📦 非遗/文物页面相关修改,显示关联列表

imengyu 1 kuukausi sitten
vanhempi
commit
87c1634583

+ 3 - 3
src/api/CommonContent.ts

@@ -295,9 +295,9 @@ export class CommonContentApi extends AppServerRequestModule<DataModel> {
     this.debugName = debugName;
   }
 
-  protected mainBodyId: number;
-  protected mainBodyColumnId?: number;
-  protected modelId: number;
+  public mainBodyId: number;
+  public mainBodyColumnId?: number;
+  public modelId: number;
   protected debugName: string;
 
   private toStringArray(arr: number|number[]|undefined) {

+ 35 - 0
src/common/composeabe/TabControl.ts

@@ -0,0 +1,35 @@
+import { computed, ref, watch } from "vue";
+
+export interface TabControlItem {
+  name: string,
+  [key: string]: any,
+}
+
+export function useTabControl(options: {
+  tabs?: TabControlItem[],
+  onTabChange?: (tab: number, tabId: number) => void,
+}) {
+
+  const tabCurrentIndex = ref(0)
+  const tabCurrentId = ref(0)
+  const tabsArray = ref<TabControlItem[]>(options.tabs ?? []);
+
+  watch(tabCurrentIndex, (v) => {
+    options.onTabChange?.(v, tabCurrentId.value) 
+  })
+
+  const tabs = computed(() => {
+    return tabsArray.value.filter(t => t.visible !== false)
+  })
+
+  return {
+    tabCurrentId,
+    tabCurrentIndex,
+    tabs,
+    tabsArray,
+    onTabClick(e: any) {
+      tabCurrentIndex.value = e.index
+      tabCurrentId.value = e.id
+    }
+  }
+}

+ 8 - 2
src/common/scss/common.scss

@@ -34,6 +34,12 @@
   }
 }
 
-.font-songti {
-  font-family: "SongtiSCBlack";
+
+//Fix
+
+.u-tag, .u-tag-wrapper {
+  flex-shrink: 0;
+}
+.u-tag__text {
+  white-space: nowrap;
 }

+ 5 - 0
src/common/scss/fonts.scss

@@ -1,3 +1,8 @@
+
+.font-songti {
+  font-family: "SongtiSCBlack";
+}
+
 @font-face {
   font-family: "iconfont"; /* Project id 4577886 */
   src:

+ 29 - 12
src/pages/article/common/CommonListPage.vue

@@ -1,6 +1,11 @@
 <template>
   <!-- 通用列表页 -->
-  <view class="common-list-page d-flex flex-column bg-base">
+  <view 
+    :class="[
+      'common-list-page d-flex flex-column', 
+      hasBg ? 'bg-base p-3' : ''
+    ]"
+  >
     <u-tabs 
       v-if="tabs"
       :list="tabs" 
@@ -20,7 +25,7 @@
       @click="handleTabClick"
     />
     <!-- 搜索 -->
-    <view v-if="showSearch" class="d-flex flex-col p-2">
+    <view v-if="showSearch" class="d-flex flex-col">
       <uni-search-bar 
         v-model="searchValue"
         radius="100" 
@@ -34,7 +39,7 @@
     <!-- 下拉框 -->
     <view 
       v-if="dropDownNames && dropDownNames.length > 0" 
-      class="d-flex flex-row justify-around p-2 pt-0"
+      class="d-flex flex-row justify-around mt-2"
     >
       <template v-for="(drop, k) in dropDownNames" :key="k" >
         <SimpleDropDownPicker 
@@ -46,9 +51,9 @@
       </template>
     </view>
     <!-- 列表 -->
-    <view class="d-flex flex-row flex-wrap justify-between p-3 pt-0">
+    <view class="d-flex flex-row flex-wrap justify-between mt-3">
       <view
-        v-for="item in listLoader.list.value"
+        v-for="(item, i) in listLoader.list.value"
         :key="item.id"
         :class="[
           itemType.endsWith('-2') ? 'width-1-2' : 'w-100'
@@ -56,18 +61,18 @@
       >
         <Box2LineLargeImageUserShadow 
           v-if="itemType.startsWith('image-large')"
-          classNames="ml-2 mb-3"
           titleColor="title-text"
-          :image="item.thumbnail || item.image"
+          :classNames="getItemClass(i)"
+          :image="getImage(item)"
           :title="item.title"
           :desc="item.desc"
           @click="goDetails(item.id)"
         />
         <Box2LineImageRightShadow 
           v-else-if="itemType.startsWith('article-common')"
-          classNames="ml-2 mb-3"
           titleColor="title-text"
-          :image="item.thumbnail || item.image"
+          :classNames="getItemClass(i)"
+          :image="getImage(item)"
           :title="item.title"
           :desc="item.desc"
           :wideImage="true"
@@ -75,9 +80,9 @@
         />
         <Box2LineImageRightShadow 
           v-else-if="itemType.startsWith('article-character')"
-          classNames="ml-2 mb-3"
+          :classNames="getItemClass(i)"
+          :image="getImage(item)"
           titleColor="title-text"
-          :image="item.thumbnail || item.image"
           :title="item.title"
           :tags="item.keywords"
           :desc="item.desc"
@@ -98,6 +103,14 @@ import SimplePageListLoader from '@/common/components/SimplePageListLoader.vue';
 import Box2LineLargeImageUserShadow from '@/pages/parts/Box2LineLargeImageUserShadow.vue';
 import Box2LineImageRightShadow from '@/pages/parts/Box2LineImageRightShadow.vue';
 import SimpleDropDownPicker, { type SimpleDropDownPickerItem } from '@/common/components/SimpleDropDownPicker.vue';
+import AppCofig from '@/common/config/AppCofig';
+
+function getImage(item: any) {
+  return item.thumbnail || item.image || AppCofig.defaultImage
+}
+function getItemClass(index: number) {
+  return props.itemType.endsWith('-2') ? (index % 2 != 0 ? 'ml-1' : 'mr-1') : ''
+}
 
 export interface DropDownNames {
   options: SimpleDropDownPickerItem[],
@@ -187,7 +200,11 @@ const props = defineProps({
   detailsParams: {
     type: Object as PropType<Record<string, any>>,
     default: () => ({})
-  }
+  },
+  hasBg: {
+    type: Boolean,
+    default: true,
+  },
 })
 
 const dropDownValues = ref<any>([]);

+ 186 - 0
src/pages/article/common/DetailTabPage.vue

@@ -0,0 +1,186 @@
+<template>
+  <!-- TAB分页的详情页 -->
+  <view class="d-flex flex-col bg-base">
+    <SimplePageContentLoader :loader="loader">
+      <template v-if="loader.content.value">
+        <view class="d-flex flex-col">
+
+          <!-- 轮播大图 -->
+          <ImageSwiper v-if="showHead" :images="loader.content.value.images" />
+
+          <!-- 标题区域 -->
+          <view class="d-flex flex-col mt-3 p-3">
+            <slot name="title" :content="loader.content.value">
+              <view class="d-flex flex-col">
+                <view class="d-flex flex-row align-center">
+                  <text class="size-lll font-songti font-bold color-text-content flex-shrink-1 mr-2">{{ loader.content.value.title }}</text>
+                  <slot name="titleEnd" :content="loader.content.value" />
+                </view>
+                <text class="size-base color-text-content-second mt-2">{{ loader.content.value.desc }}</text>
+              </view>
+            </slot>
+            <slot name="titleExtra" :content="loader.content.value" />
+          </view>
+
+          <!-- 内容切换标签 -->
+          <view class="ml-2 mr-2">
+            <u-tabs 
+              :list="tabs" 
+              :current="tabCurrentIndex"
+              lineWidth="30"
+              lineColor="#d9492e"
+              :activeStyle="{
+                color: '#000',
+                fontWeight: 'bold',
+                transform: 'scale(1.05)'
+              }"
+              :inactiveStyle="{
+                color: '#606266',
+                transform: 'scale(1)'
+              }"
+              :scrollable="tabs.length >= 4"
+              class="top-tab"
+              @click="onTabClick"
+            />
+          </view>
+
+          <view class="d-flex flex-col radius-l bg-light p-25 mt-3" style="min-height:70vh">
+            <!-- 简介 -->
+            <template v-if="tabCurrentId == 0">
+              <u-parse 
+                v-if="loader.content.value.intro"
+                :content="loader.content.value.intro"
+                :tagStyle="commonParserStyle"
+              />
+              <u-parse 
+                v-if="loader.content.value.content"
+                :content="loader.content.value.content"
+                :tagStyle="commonParserStyle"
+              />
+            </template>
+            <!-- 图片 -->
+            <template v-else-if="tabCurrentId == 1">
+              <ImageGrid
+                :images="loader.content.value.images"
+                :rowCount="2"
+                :preview="true"
+                imageHeight="200rpx"
+              />
+            </template>
+            <!-- 视频 -->
+            <template v-else-if="tabCurrentId == 2">
+              <video
+                v-if="loader.content.value.video"
+                class="w-100 video"
+                autoplay
+                :poster="loader.content.value.image"
+                :src="loader.content.value.video"
+                controls
+              />
+            </template>
+            <!-- 音频 -->
+            <template v-else-if="tabCurrentId == 3">
+              <video 
+                v-if="loader.content.value.audio"
+                class="w-100 video"
+                autoplay
+                :poster="loader.content.value.image"
+                :src="loader.content.value.audio"
+                controls
+              />
+            </template>
+            <!-- 其他tab -->
+            <slot v-else name="extraTabs" :tabCurrentId="tabCurrentId" :content="loader.content.value" />
+          </view>
+          <ContentNote />
+        </view>
+      </template>
+    </SimplePageContentLoader>
+  </view>
+</template>
+<script setup lang="ts">
+import type { GetContentDetailItem } from "@/api/CommonContent";
+import { useSimplePageContentLoader } from "@/common/composeabe/SimplePageContentLoader";
+import { useLoadQuerys } from "@/common/composeabe/LoadQuerys";
+import { useTabControl, type TabControlItem } from "@/common/composeabe/TabControl";
+import SimplePageContentLoader from "@/common/components/SimplePageContentLoader.vue";
+import ImageGrid from "@/pages/parts/ImageGrid.vue";
+import ImageSwiper from "@/pages/parts/ImageSwiper.vue";
+import ContentNote from "@/pages/parts/ContentNote.vue";
+import commonParserStyle from "@/common/style/commonParserStyle";
+import type { PropType, Ref } from "vue";
+
+const props = defineProps({
+  load: {
+    type: Function as PropType<(id: number, tabsArray: Ref<TabControlItem[]>) => Promise<GetContentDetailItem>>,
+    default: null,
+  },
+  extraTabs: {
+    type: Array as PropType<TabControlItem[]>,
+    default: () => [],
+  },
+  showHead: {
+    type: Boolean,
+    default: true,
+  },
+})
+
+const emit = defineEmits([
+  "tabChange"
+])
+
+const loader = useSimplePageContentLoader<
+  GetContentDetailItem, 
+  { id: number }
+>(async (params) => {
+  if (!params)
+    throw new Error("!params");
+  const d = await props.load(params.id, tabsArray);
+  tabsArray.value[1].visible = Boolean(d.images && d.images.length > 1);
+  tabsArray.value[2].visible = Boolean(d.video);
+  tabsArray.value[3].visible = Boolean(d.audio);
+  return d;
+});
+
+const { 
+  tabCurrentId,
+  tabCurrentIndex,
+  tabsArray,
+  tabs,
+  onTabClick
+} = useTabControl({
+  tabs: [
+    {
+      id: 0,
+      name: '简介',
+      visible: true,
+    },
+    {
+      id: 1,
+      name: '图片',
+      visible: true,
+    },
+    {
+      id: 2,
+      name: '视频',
+      visible: true,
+    },
+    {
+      id: 3,
+      name: '音频',
+      visible: true,
+    },
+    ...props.extraTabs,
+  ],
+  onTabChange(a, b) {
+    emit("tabChange", a, b);
+  },
+})
+
+useLoadQuerys({ id : 0 }, (p) => loader.loadData(p));
+
+</script>
+
+<style lang="scss">
+
+</style>

+ 130 - 0
src/pages/article/common/IntroBlock.vue

@@ -0,0 +1,130 @@
+<template>
+  <view :class="[
+    'intro-block',
+    small ? 'small' : '',
+  ]">
+    <HomeTitle v-if="title" :title="title" />
+    <view class="desc no-indent">
+      <view v-if="address" class="navigation">
+        <view class="address">
+          <text class="iconfont icon-navigation"></text>
+          <text>{{ address }}</text>
+        </view>
+        <view class="link" @click="emit('navTo')">
+          去这里 <text class="iconfont icon-go"></text>
+        </view>
+      </view>
+      <view 
+        v-for="(it, k) in descItems" 
+        :key="k"
+        :class="['entry',Boolean(it.value)?'':'hidden']"
+      >
+        <view class="label">{{ it.label }}</view>
+        <view class="value">{{ it.value }}</view>
+      </view>
+      <slot name="lastDesc" />
+    </view>
+    <slot />
+  </view>
+</template>
+
+<script setup lang="ts">
+import HomeTitle from '@/pages/parts/HomeTitle.vue';
+import type { PropType } from 'vue';
+
+const props = defineProps({	
+  title: {
+    type: String,
+    default: ''
+  },
+  address: {
+    type: String,
+    default: ''
+  },
+  descItems: {
+    type: Array as PropType<Array<{ label: string, value: any }>>,
+    default: () => []
+  },
+  small: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const emit = defineEmits([	
+  "navTo"	
+])
+</script>
+
+<style lang="scss">
+.intro-block {
+  margin-bottom: 38rpx;
+
+  &.small {
+    margin-bottom: 0rpx;
+
+    .desc{
+      line-height: inherit;
+      padding-bottom: 10rpx;
+    }
+  }
+
+  .entry {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    margin-bottom: 10rpx;
+
+    &.hidden {
+      display: none;
+    }
+
+    .label {
+      color: #666666;
+      font-weight: 400;
+      font-size: 28rpx;
+    }
+    .value {
+      font-size: 28rpx;
+      color: #312520;
+      font-weight: 400;
+    }
+  }
+  .sub-title{
+    margin-left: 20rpx;
+    margin-top: 10rpx;
+    font-size: 30rpx;
+    font-weight: 600;
+  }
+  .desc{
+    padding:30rpx;
+  }
+  .navigation{
+    display: flex;
+    align-items: center;
+    margin-bottom: 28rpx;
+    .address{
+      flex:1;
+      height: auto;
+      background: #F9F6EB;
+      border-radius: 28rpx;
+      font-weight: 400;
+      font-size: 24rpx;
+      color: #000000;
+      line-height: 48rpx;
+      padding-left: 30rpx;
+      display: flex;
+      align-items: center;
+      text.iconfont{
+        display: inline-block;
+        font-size: 36rpx;
+        margin-right: 8rpx;
+      }
+    }
+    .link{
+      margin-left: 20rpx;
+      color:#FF8719;
+    }
+  }
+}
+</style>

+ 2 - 8
src/pages/home.vue

@@ -149,7 +149,7 @@
             </text> 
             <image
               class="w-100 height-250 radius-base"
-              :src="tab.thumbnail || tab.image"
+              :src="tab.thumbnail || tab.image || AppCofig.defaultImage"
               mode="aspectFill"
             />
           </view>
@@ -227,13 +227,7 @@ const subTabs2 = [
   { 
     name: '文物古迹', 
     icon: MainBoxIcon5 , 
-    onClick: () => navTo('/pages/article/common/list', {
-      title: '文物古迹',
-      mainBodyColumnId: 252,
-      modelId: 3,
-      itemType: 'article-common',
-      detailsPage: '/pages/article/details',
-    }) 
+    onClick: () => navTo('/pages/inhert/artifact/list') 
   },
   { 
     name: '非遗项目', 

+ 106 - 183
src/pages/inhert/artifact/details.vue

@@ -1,131 +1,122 @@
 <template>
-  <view class="d-flex flex-col bg-base">
-    <SimplePageContentLoader :loader="loader">
-      <template v-if="loader.content.value">
-        <view class="banner">
-          <swiper class="swiper" circular :indicator-dots="true" :autoplay="true" :interval="2000" :duration="1000">
-            <swiper-item v-for="(item, key) in loader.content.value.images" :key="key">
-              <view 
-                class="item"
-                @click="onPreviewImage(key)"
-              >
-                <ImageWrapper 
-                  :src="item"
-                  mode="aspectFill"
-                  width="750rpx"
-                />
-              </view>
-            </swiper-item>
-          </swiper>
-        </view>
-        <view class="d-flex flex-col p-3 bg-light">
-          <view class="intro-block artifact-info">
-            <HomeTitle :title="loader.content.value.title" />
-            <view class="desc no-indent">
-              <view v-if="loader.content.value.address" class="navigation">
-                <view class="address">
-                  <text class="iconfont icon-navigation"></text>
-                  <text>{{ loader.content.value.address }}</text>
-                </view>
-                <view class="link" @click="handleNavTo">
-                  去这里 <text class="iconfont icon-go"></text>
-                </view>
-              </view>
-              <view v-if="loader.content.value.openStatusText" class="entry">
-                <view class="label">开放时间:</view>
-                <view class="value">{{ loader.content.value.openStatusText }}</view>
-              </view>
-              <view v-if="loader.content.value.age" class="entry">
-                <view class="label">年代:</view>
-                <view class="value">{{ loader.content.value.age }}</view>
-              </view>
-              <view class="entry">
-                <view class="label">文物类型:</view>
-                <view class="value">{{ loader.content.value.crTypeText }}</view>
-              </view>
-              <view class="entry">
-                <view class="label">所属区域:</view>
-                <view class="value">{{ loader.content.value.regionText }}</view>
-              </view>
-              <view class="entry">
-                <view class="label">级别:</view>
-                <view class="value">{{ loader.content.value.levelText }}</view>
-              </view>
-              <view v-if="loader.content.value.protectedArea" class="entry">
-                <view class="label">保护范围:</view>
-                <view class="value">
-                </view>
-              </view>
-              <u-parse :content="loader.content.value.protectedArea" :tagStyle="commonParserStyle"></u-parse>
-            </view>
-          </view>
-          <view class="intro-block">
-            <view class="title">
-              <view class="line"></view>
-              <text class="sm">简介</text>
-            </view>
-            <view class="desc">
-              <u-parse :content="loader.content.value.intro" :tagStyle="commonParserStyle"></u-parse>
-            </view>
-            <view class="desc">
-              <u-parse :content="loader.content.value.value" :tagStyle="commonParserStyle"></u-parse>
-            </view>
-          </view>
-          <ContentNote />
-          <!-- <HomeTitle title="更多人气好去处" />
-          <view class="rec-list">
-            <swiper class="swiper" :autoplay="false" :circular="false" :duration="500" :next-margin="'145rpx'">
-              <swiper-item v-for="item in loader.content.value.associationMeList" :key="item.id" @click="goDetails(item)">
-                <view class="item">
-                  <image :src="item.image" mode="aspectFill"></image>
-                  <view class="name">
-                    <text>{{ item.title }}</text>
-                  </view>
-                </view>
-              </swiper-item>
-            </swiper>
-          </view> -->
+  <DetailTabPage
+    :load="load"
+    :extraTabs="[
+      {
+        id: 5,
+        name: 'VR参观',
+        visible: true,
+      },
+      {
+        id: 6,
+        name: '保护范围',
+        visible: true,
+      },
+      {
+        id: 7,
+        name: '建筑环境',
+        visible: true,
+      },
+      {
+        id: 8,
+        name: '价值评估',
+        visible: true,
+      },
+    ]"
+  >
+    <template #extraTabs="{ content, tabCurrentId }">
+      <template v-if="tabCurrentId==5">
+        <!-- VR参观 -->
+        <view class="d-flex flex-row justify-center p-5">
+          <u-button @click="handleGoToVr(content.vr as string)">
+            <text class="iconfont icon-go"></text>
+            点击参观
+          </u-button>
         </view>
       </template>
-    </SimplePageContentLoader>
-  </view>
+      <template v-if="tabCurrentId==6">
+        <!-- 保护范围 -->
+        <u-parse :content="content.protectedArea" :tagStyle="commonParserStyle" />
+      </template>
+      <template v-if="tabCurrentId==7">
+        <!-- 建筑环境 -->
+        <u-parse :content="content.environment" :tagStyle="commonParserStyle" />
+      </template>
+      <template v-if="tabCurrentId==8">
+        <!-- 价值评估 -->
+        <u-parse :content="content.value" :tagStyle="commonParserStyle" />
+      </template>
+    </template>
+    <template #titleEnd="{ content }">
+      <u-tag 
+        v-if="content.levelText"
+        :text="content.levelText"
+        size="mini" plain color="#d9492e"
+        class="flex-shrink-0"
+      />
+    </template>
+    <template #titleExtra="{ content }">
+      <IntroBlock 
+        small
+        :address="(content.address as string)"
+        :descItems="[
+          {
+            label: '开放时间',
+            value: content.openStatusText
+          },
+          {
+            label: '年代',
+            value: content.age 
+          },
+          {
+            label: '级别',
+            value: content.levelText 
+          },
+          {
+            label: '所属区域',
+            value: content.regionText ,
+          },
+          {
+            label: '文物类型',
+            value: content.crTypeText, 
+          },
+          {
+            label: '文物编码',
+            value: content.code,
+          }
+        ]"
+        @navTo="handleNavTo(content)"
+      />
+    </template>
+  </DetailTabPage>
 </template>
 <script setup lang="ts">
-import type { GetContentDetailItem } from "@/api/CommonContent";
-import { useSimplePageContentLoader } from "@/common/composeabe/SimplePageContentLoader";
-import { onLoad } from "@dcloudio/uni-app";
+import type { TabControlItem } from "@/common/composeabe/TabControl";
+import type { Ref } from "vue";
 import { navTo } from "@/common/utils/PageAction";
 import UnmoveableContent from "@/api/inheritor/UnmoveableContent";
-import SimplePageContentLoader from "@/common/components/SimplePageContentLoader.vue";
-import ImageWrapper from "@/common/components/ImageWrapper.vue";
-import HomeTitle from "@/pages/parts/HomeTitle.vue";
 import commonParserStyle from "@/common/style/commonParserStyle";
-import { useLoadQuerys } from "@/common/composeabe/LoadQuerys";
-import { useSwiperImagePreview } from "@/common/composeabe/SwiperImagePreview";
-import ContentNote from "@/pages/parts/ContentNote.vue";
+import IntroBlock from "@/pages/article/common/IntroBlock.vue";
+import DetailTabPage from "@/pages/article/common/DetailTabPage.vue";
 
-const loader = useSimplePageContentLoader<
-  GetContentDetailItem, 
-  { id: number }
->(async (params) => {
-  if (!params)
-    throw new Error("!params");
-  return await UnmoveableContent.getContentDetail(params.id);
-});
-
-useLoadQuerys({ id : 0, }, (p) => loader.loadData(p));
-
-const { onPreviewImage } = useSwiperImagePreview(() => loader.content.value?.images || [])
+async function load(id: number, tabsArray: Ref<TabControlItem[]>) {
+  const d = await UnmoveableContent.getContentDetail(id);
+  tabsArray.value[4].visible = Boolean(d.vr);
+  tabsArray.value[5].visible = Boolean(d.protectedArea);
+  tabsArray.value[6].visible = Boolean(d.environment);
+  tabsArray.value[7].visible = Boolean(d.value);
+  return d;
+}
 
-function handleNavTo() {
+function handleGoToVr(vr: string) {
+  navTo('/pages/article/web/ewebview', { url: vr })
+}
+function handleNavTo(content: any) {
   navTo('../../travel/nav/navto', {
-    latitude: loader.content.value?.latitude,  
-    longitude: loader.content.value?.longitude,  
+    latitude: content?.latitude,  
+    longitude: content?.longitude,  
   }) 
 }
-function goDetails(item: any) {
-  navTo('./details', { id: item.id })
-}
 </script>
 
 <style lang="scss" scoped>
@@ -157,74 +148,6 @@ function goDetails(item: any) {
     }
   }
 }
-
-.intro-block {
-  margin-bottom: 38rpx;
-  .title{
-    .sm{
-      font-size: 30rpx;
-    }
-  }
-  &.artifact-info{
-    .desc{
-      line-height: inherit;
-      padding-bottom: 10rpx;
-    }
-  }
-  .entry {
-    display: flex;
-    flex-direction: row;
-    justify-content: space-between;
-    margin-bottom: 10rpx;
-
-    .label {
-      color: #666666;
-      font-weight: 400;
-      font-size: 28rpx;
-    }
-    .value {
-      font-size: 28rpx;
-      color: #312520;
-      font-weight: 400;
-    }
-  }
-  .sub-title{
-    margin-left: 20rpx;
-    margin-top: 10rpx;
-    font-size: 30rpx;
-    font-weight: 600;
-  }
-  .desc{
-    padding:30rpx;
-  }
-  .navigation{
-    display: flex;
-    align-items: center;
-    margin-bottom: 28rpx;
-    .address{
-      flex:1;
-      height: auto;
-      background: #F9F6EB;
-      border-radius: 28rpx;
-      font-weight: 400;
-      font-size: 24rpx;
-      color: #000000;
-      line-height: 48rpx;
-      padding-left: 30rpx;
-      display: flex;
-      align-items: center;
-      text.iconfont{
-        display: inline-block;
-        font-size: 36rpx;
-        margin-right: 8rpx;
-      }
-    }
-    .link{
-      margin-left: 20rpx;
-      color:#FF8719;
-    }
-  }
-}
 .banner {
   margin-top: 0;
   .swiper {

+ 2 - 1
src/pages/inhert/artifact/list.vue

@@ -65,6 +65,7 @@ import SimplePageListLoader from '@/common/components/SimplePageListLoader.vue';
 import Box2LineLargeImageUserShadow from '@/pages/parts/Box2LineLargeImageUserShadow.vue';
 import SimpleDropDownPicker from '@/common/components/SimpleDropDownPicker.vue';
 import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import AppCofig from '@/common/config/AppCofig';
 
 const categoryData = useSimpleDataLoader(async () => 
   [{
@@ -126,7 +127,7 @@ const listLoader = useSimplePageListLoader<{
   return res.list.map((item) => {
     return {
       id: item.id,
-      image: item.thumbnail || item.image,
+      image: item.thumbnail || item.image || AppCofig.defaultImage,
       name: item.title,
     }
   })

+ 98 - 48
src/pages/inhert/inheritor/details.vue

@@ -1,60 +1,110 @@
 <template>
-  <view class="d-flex flex-col bg-base">
-    <SimplePageContentLoader :loader="loader">
-      <template v-if="loader.content.value">
-        <view class="d-flex flex-col p-3">
-          <view class="d-flex flex-row justify-between">
-            <view class="d-flex flex-col">
-              <text class="size-lll font-bold color-text-content">{{ loader.content.value.title }}</text>
-              <text class="size-base color-text-content-second mt-2">{{ loader.content.value.birthplace || loader.content.value.desc }}</text>
-              <RoundTags
-                :tags="loader.content.value.tags"
-                :tags2="[
-                  loader.content.value.age as string,
-                  loader.content.value.nation as string
-                ]"
-              />
-            </view>
-            <image 
-              class="width-150 height-150 radius-base flex-shrink-0" 
-              :src="loader.content.value.image" mode="aspectFill"
+  <DetailTabPage
+    :load="load"
+    :extraTabs="[
+      {
+        id: 5,
+        name: '获得奖项',
+        visible: true,
+      },
+      {
+        id: 6,
+        name: '非遗项目',
+        visible: true,
+      }
+    ]"
+    :showHead="false"
+  >
+    <template #extraTabs="{ content, tabCurrentId }">
+      <template v-if="tabCurrentId==5">
+        <!-- 获得奖项 -->
+        <u-parse :content="content.prize" :tagStyle="commonParserStyle"></u-parse>
+      </template>
+      <template v-else-if="tabCurrentId==6">
+        <!-- 非遗项目 -->
+        <CommonListPage 
+          :showSearch="false"
+          :hasBg="false"
+          :load="(page: number, pageSize: number) => loadSubList(page, pageSize, content, 'associationMeList')"
+          detailsPage="/pages/inhert/intangible/details"
+          :detailsParams="{
+            mainBodyColumnId: ProjectsContent.mainBodyColumnId,
+            modelId: ProjectsContent.modelId,
+          }"
+        />
+      </template>
+    </template>
+    <template #title="{ content }">
+      <view class="d-flex flex-col">
+        <view class="d-flex flex-row justify-between">
+          <view class="d-flex flex-col">
+            <text class="size-lll font-bold color-text-content">{{ content.title }}</text>
+            <text class="size-base color-text-content-second mt-2">{{ content.birthplace || content.desc }}</text>
+            <RoundTags
+              :tags="content.tags"
+              :tags2="[
+                content.age as string,
+                content.nation as string
+              ]"
             />
           </view>
-          <view class="d-flex flex-col radius-l bg-light p-25 mt-3">
-            <u-parse :content="loader.content.value.intro" :tagStyle="commonParserStyle"></u-parse>
-            <u-parse :content="loader.content.value.content" :tagStyle="commonParserStyle"></u-parse>
-            <u-parse :content="loader.content.value.value" :tagStyle="commonParserStyle"></u-parse>
-          </view>
-          <ContentNote />
+          <image 
+            class="width-150 height-150 radius-base flex-shrink-0" 
+            :src="content.image" mode="aspectFill"
+          />
         </view>
-      </template>
-    </SimplePageContentLoader>
-  </view>
+      </view> 
+    </template>
+    <template #titleExtra="{ content }">
+      <IntroBlock
+        small
+        :descItems="[
+          {
+            label: '生日',
+            value: content.dateBirth,
+          }, 
+          {
+            label: '性别',
+            value: content.gender == '1'? '男' : '女', 
+          },
+          {
+            label: '出生地',
+            value: content.birthplace,
+          },
+          {
+            label: '民族',
+            value: content.nation,
+          },
+          {
+            label: '单位',
+            value: content.unit,
+          }
+        ]"
+      />
+    </template>
+  </DetailTabPage>
 </template>
 <script setup lang="ts">
-import type { GetContentDetailItem } from "@/api/CommonContent";
-import { useSimplePageContentLoader } from "@/common/composeabe/SimplePageContentLoader";
-import { onLoad } from "@dcloudio/uni-app";
-import { navTo } from "@/common/utils/PageAction";
-import UnmoveableContent from "@/api/inheritor/UnmoveableContent";
-import SimplePageContentLoader from "@/common/components/SimplePageContentLoader.vue";
-import ImageWrapper from "@/common/components/ImageWrapper.vue";
+import type { TabControlItem } from "@/common/composeabe/TabControl";
+import type { Ref } from "vue";
 import RoundTags from "@/pages/parts/RoundTags.vue";
 import commonParserStyle from "@/common/style/commonParserStyle";
-import { useLoadQuerys } from "@/common/composeabe/LoadQuerys";
-import ContentNote from "@/pages/parts/ContentNote.vue";
-
-const loader = useSimplePageContentLoader<
-  GetContentDetailItem, 
-  { id: number }
->(async (params) => {
-  if (!params)
-    throw new Error("!params");
-  return await UnmoveableContent.getContentDetail(params.id);
-});
-
-useLoadQuerys({ id : 0 }, (p) => loader.loadData(p));
+import InheritorContent from "@/api/inheritor/InheritorContent";
+import ProjectsContent from "@/api/inheritor/ProjectsContent";
+import IntroBlock from "@/pages/article/common/IntroBlock.vue";
+import DetailTabPage from "@/pages/article/common/DetailTabPage.vue";
+import CommonListPage from "@/pages/article/common/CommonListPage.vue";
 
+async function load(id: number, tabsArray: Ref<TabControlItem[]>) {
+  const d = await InheritorContent.getContentDetail(id);
+  tabsArray.value[4].visible = Boolean(d.prize);
+  tabsArray.value[5].visible = Boolean(d.associationMeList && d.associationMeList.length > 0);
+  return d;
+}
+async function loadSubList(page: number, pageSize: number, content: any, subList: string) {
+  return (content[subList] as any[] || [])
+    .slice((page - 1) * pageSize, page * pageSize)
+}
 </script>
 
 <style lang="scss">

+ 98 - 71
src/pages/inhert/intangible/details.vue

@@ -1,80 +1,107 @@
 <template>
-  <view class="d-flex flex-col bg-base">
-    <SimplePageContentLoader :loader="loader">
-      <template v-if="loader.content.value">
-        <view class="d-flex flex-col">
-          <video 
-            v-if="loader.content.value.audio"
-            class="video w-100"
-            autoplay
-            :poster="loader.content.value.image"
-            :src="loader.content.value.audio"
-            controls
-          />
-          <video
-            v-else-if="loader.content.value.video"
-            class="video w-100"
-            autoplay
-            :poster="loader.content.value.image"
-            :src="loader.content.value.video"
-            controls
-          />
-          <ImageSwiper v-else :images="loader.content.value.images" />
-          <view class="d-flex flex-col text-align-center mt-3 p-3">
-            <text class="size-lll font-bold color-text-content">{{ loader.content.value.title }}</text>
-            <text class="size-base color-text-content-second mt-2">{{ loader.content.value.desc }}</text>
-          </view>
-
-          <view class="d-flex flex-col radius-l bg-light p-25 mt-3">
-            <u-parse 
-              v-if="loader.content.value.intro"
-              :content="loader.content.value.intro"
-              :tagStyle="commonParserStyle"
-            ></u-parse>
-            <u-parse 
-              v-if="loader.content.value.content"
-              :content="loader.content.value.content"
-              :tagStyle="commonParserStyle"
-            ></u-parse>
-            <u-parse 
-              v-if="loader.content.value.value"
-              :content="loader.content.value.value"
-              :tagStyle="commonParserStyle"
-            ></u-parse>
-          </view>
-
-          <ContentNote />
-        </view>
+  <DetailTabPage
+    :load="load"
+    :extraTabs="[
+      {
+        id: 5,
+        name: '传习所',
+        visible: true,
+      },
+      {
+        id: 6,
+        name: '传承人',
+        visible: true,
+      },
+      {
+        id: 7,
+        name: '作品(产品)',
+        visible: true,
+      }
+    ]"
+  >
+    <template #extraTabs="{ content, tabCurrentId }">
+      <template v-if="tabCurrentId==5">
+        <!-- 非遗传习中心 -->
+        <CommonListPage 
+          :showSearch="false"
+          :hasBg="false"
+          :load="(page: number, pageSize: number) => loadSubList(page, pageSize, content, 'ichSitesList')"
+          :detailsParams="{
+            mainBodyColumnId: SeminarContent.mainBodyColumnId,
+            modelId: SeminarContent.modelId,
+          }"
+        />
+      </template>
+      <template v-else-if="tabCurrentId==6">
+        <!-- 非遗传承人 -->
+        <CommonListPage 
+          :showSearch="false"
+          :hasBg="false"
+          :load="(page: number, pageSize: number) => loadSubList(page, pageSize, content, 'inheritorsList')"
+          detailsPage="/pages/inhert/inheritor/details"
+          :detailsParams="{
+            mainBodyColumnId: InheritorContent.mainBodyColumnId,
+            modelId: InheritorContent.modelId,
+          }"
+        />
       </template>
-    </SimplePageContentLoader>
-  </view>
+      <template v-else-if="tabCurrentId==7">
+        <!-- 非遗产品(作品) -->
+        <CommonListPage 
+          :showSearch="false"
+          :hasBg="false"
+          :load="(page: number, pageSize: number) => loadSubList(page, pageSize, content, 'associationMeList')"
+          detailsPage="/pages/inhert/intangible/details"
+          :detailsParams="{
+            mainBodyColumnId: ProductsContent.mainBodyColumnId,
+            modelId: ProductsContent.modelId,
+          }"
+        />
+      </template>
+    </template>
+    <template #titleEnd="{ content }">
+      <u-tag 
+        v-if="content.levelText"
+        :text="content.levelText"
+        size="mini" plain color="#d9492e"
+        class="flex-shrink-0"
+      />
+    </template>
+    <template #titleExtra="{ content }">
+      <view class="d-flex flex-col">
+        <text class="color-text-content-second size-s">
+          <text class="iconfont icon-place mr-2"></text>
+          {{ content.address || content.regionText }}
+        </text>
+        <text class="color-text-content-second size-s">
+          <text class="iconfont icon-go mr-2"></text>
+          {{ content.ichTypeText || '非遗项目' }}
+        </text>
+      </view>
+    </template>
+  </DetailTabPage>
 </template>
 <script setup lang="ts">
-import type { GetContentDetailItem } from "@/api/CommonContent";
-import { useSimplePageContentLoader } from "@/common/composeabe/SimplePageContentLoader";
-import { onLoad } from "@dcloudio/uni-app";
-import { navTo } from "@/common/utils/PageAction";
-import UnmoveableContent from "@/api/inheritor/UnmoveableContent";
-import SimplePageContentLoader from "@/common/components/SimplePageContentLoader.vue";
-import ImageWrapper from "@/common/components/ImageWrapper.vue";
-import RoundTags from "@/pages/parts/RoundTags.vue";
-import commonParserStyle from "@/common/style/commonParserStyle";
-import { useLoadQuerys } from "@/common/composeabe/LoadQuerys";
-import ImageSwiper from "@/pages/parts/ImageSwiper.vue";
-import ContentNote from "@/pages/parts/ContentNote.vue";
+import DetailTabPage from "@/pages/article/common/DetailTabPage.vue";
+import ProjectsContent from "@/api/inheritor/ProjectsContent";
+import CommonListPage from "@/pages/article/common/CommonListPage.vue";
+import type { TabControlItem } from "@/common/composeabe/TabControl";
+import type { Ref } from "vue";
+import InheritorContent from "@/api/inheritor/InheritorContent";
+import ProductsContent from "@/api/inheritor/ProductsContent";
+import SeminarContent from "@/api/inheritor/SeminarContent";
 
-const loader = useSimplePageContentLoader<
-  GetContentDetailItem, 
-  { id: number }
->(async (params) => {
-  if (!params)
-    throw new Error("!params");
-  const d = await UnmoveableContent.getContentDetail(params.id);
-  console.log(d)
+async function load(id: number, tabsArray: Ref<TabControlItem[]>) {
+  const d = await ProjectsContent.getContentDetail(id);
+  tabsArray.value[4].visible = Boolean(d.ichSitesList && (d.ichSitesList as any[]).length > 0);
+  tabsArray.value[5].visible = Boolean(d.inheritorsList && (d.inheritorsList as any[]).length > 0);
+  tabsArray.value[6].visible = Boolean(d.associationMeList && (d.associationMeList as any[]).length > 0);
   return d;
-});
-
-useLoadQuerys({ id : 0 }, (p) => loader.loadData(p));
+}
+async function loadSubList(page: number, pageSize: number, content: any, subList: string) {
+  return (content[subList] as any[] || [])
+    .slice((page - 1) * pageSize, page * pageSize)
+}
 
 </script>
 

+ 34 - 28
src/pages/inhert/map/index.vue

@@ -3,7 +3,7 @@
     
     <u-tabs 
       :list="tabs" 
-      :current="tab"
+      :current="tabCurrentIndex"
       lineWidth="30"
       lineColor="#d9492e"
       :activeStyle="{
@@ -17,7 +17,7 @@
       }"
       :scrollable="true"
       class="top-tab"
-      @click="(e: any) => tab = e.index"
+      @click="onTabClick"
     />
     <view class="d-flex flex-col p-2">
       <uni-search-bar 
@@ -31,9 +31,9 @@
       />
     </view>
     <view class="d-flex flex-row justify-around p-2 pt-0">
-      <SimpleDropDownPicker v-if="tab == 2" v-model="selectedTag" :columns="categoryData.content.value" />
-      <SimpleDropDownPicker v-if="tab <= 2" v-model="selectedLevel" :columns="levelData.content.value" />
-      <SimpleDropDownPicker v-if="tab <= 2" v-model="selectedRegion" :columns="regionData.content.value" />
+      <SimpleDropDownPicker v-if="tabCurrentIndex == 2" v-model="selectedTag" :columns="categoryData.content.value" />
+      <SimpleDropDownPicker v-if="tabCurrentIndex <= 2" v-model="selectedLevel" :columns="levelData.content.value" />
+      <SimpleDropDownPicker v-if="tabCurrentIndex <= 2" v-model="selectedRegion" :columns="regionData.content.value" />
     </view>
     <view class="d-flex flex-row flex-wrap justify-between">
       <map 
@@ -62,25 +62,34 @@ import { onLoad } from '@dcloudio/uni-app';
 import AppCofig from '@/common/config/AppCofig';
 import VillageApi from '@/api/inhert/VillageApi';
 import ScenicSpotContent from '@/api/fusion/ScenicSpotContent';
+import { useTabControl } from '@/common/composeabe/TabControl';
 
-const tab = ref(0)
-const tabs = [
-  {
-    name: '非遗项目'
+const { 
+  tabCurrentIndex ,
+  tabs,
+  onTabClick
+} = useTabControl({
+  tabs: [
+    {
+      name: '非遗项目'
+    },
+    {
+      name: '非遗传习所'
+    },
+    {
+      name: '文物古迹'
+    },
+    {
+      name: '传统村落'
+    },
+    {
+      name: '闽南文化景区'
+    },
+  ],
+  onTabChange() {
+    listLoader.loadData(undefined, true);
   },
-  {
-    name: '非遗传习所'
-  },
-  {
-    name: '文物古迹'
-  },
-  {
-    name: '传统村落'
-  },
-  {
-    name: '闽南文化景区'
-  },
-];
+})
 const mapCtx = uni.createMapContext('map');
 const categoryData = useSimpleDataLoader(async () => 
   [{
@@ -115,7 +124,7 @@ const selectedRegion = ref(0);
 const searchValue = ref('');
 const listLoader = useSimplePageListLoader(50, async (page, pageSize) => {
   let list;
-  switch (tab.value) {
+  switch (tabCurrentIndex.value) {
     default:
     case 0:
       list = (await ProjectsContent.getContentList(new GetContentListParams().setSelfValues({
@@ -199,9 +208,6 @@ watch(selectedRegion, () => {
 watch(selectedTag, () => {
   listLoader.loadData(undefined, true);
 });
-watch(tab, () => {
-  listLoader.loadData(undefined, true);
-});
 
 function doSearch() {
   listLoader.loadData(undefined, true);
@@ -210,7 +216,7 @@ function onMarkerTap(e: { markerId: number }) {
   goDetails(e.markerId);
 }
 function goDetails(id: number) {
-  switch (tab.value) {
+  switch (tabCurrentIndex.value) {
     default:
     case 0: navTo('/pages/article/details', { id }); break;
     case 1: navTo('/pages/inhert/intangible/details', { id }); break;
@@ -252,7 +258,7 @@ onLoad((query) => {
       clear: false,
     });
   })
-  tab.value = Number(query?.tab || 0) - 1;
+  tabCurrentIndex.value = Number(query?.tab || 0) - 1;
   listLoader.loadData(undefined, true);
 })
 </script>

+ 1 - 1
src/pages/parts/ContentNote.vue

@@ -2,7 +2,7 @@
   <div class="d-flex flex-row justify-content-center align-items-center p-3">
     <img class="mr-2 width-40 height-40 flex-shrink-0" src="https://mn.wenlvti.net/app_static/minnan/images/icon_info.svg" />
     <span class="color-text-content-second size-s">
-      此网站内容部分信息来源于网络,如涉及侵权,请及时联系我们进行删除。
+      此平台为公益平台,部分信息来源于网络,如涉侵权,请联系我们删除
       <br>联系邮箱:153168270@qq.com
     </span>
   </div>

+ 63 - 0
src/pages/parts/ImageGrid.vue

@@ -0,0 +1,63 @@
+<template>
+  <view class="w-100 d-flex flex-row flex-wrap" :style="{ gap: `${gap}rpx` }">
+    <image 
+      v-for="(v, k) in images"
+      :key="k"
+      :src="imagekey ? v[imagekey] : v" 
+      :style="{ 
+        width: `calc(${100 / rowCount}% - ${gap}rpx)`,
+        height: imageHeight,
+        borderRadius: '10rpx',
+      }"
+      mode="aspectFill"
+      @click="itemClick(v, k)"
+    />
+  </view>
+</template>
+
+<script setup lang="ts">
+import type { PropType } from 'vue';
+
+const props = defineProps({	
+  rowCount : {
+    type: Number,
+    default: 3,
+  },
+  imagekey : {
+    type: String,
+    default: undefined,
+  },
+  imageHeight : {
+    type: String,
+    default: undefined,
+  },
+  gap: {
+    type: Number,
+    default: 10,
+  },
+  images: {
+    type: Object as PropType<any[]>,
+    default: null,
+  },
+  preview: {  
+    type: Boolean,
+    default: false,
+  }
+})
+
+const emit = defineEmits([	
+  "itemClick"	
+])
+
+function itemClick(item: any, index: number) {
+  if (props.preview) {
+    uni.previewImage({
+      urls: props.images.map((v: any) => (props.imagekey ? v[props.imagekey] : v) || v),
+      current: (props.imagekey ? item[props.imagekey] : item) || item,
+    })
+  } else {
+    emit('itemClick', item, index);
+  }
+}
+
+</script>