快乐的梦鱼 vor 4 Tagen
Ursprung
Commit
0502caa9a2
34 geänderte Dateien mit 1082 neuen und 682 gelöschten Zeilen
  1. 1 0
      src/App.vue
  2. 7 0
      src/api/CommonContent.ts
  3. 0 43
      src/pages.json
  4. 0 6
      src/pages/article/common/DetailTabPage.ts
  5. 4 81
      src/pages/article/common/DetailTabPage.vue
  6. 3 3
      src/pages/article/common/DetailsCommon.vue
  7. 2 1
      src/pages/article/data/CommonCategoryBlocks.ts
  8. 52 28
      src/pages/article/data/CommonCategoryBlocks.vue
  9. 1 0
      src/pages/article/data/CommonCategoryDefine.ts
  10. 40 11
      src/pages/article/data/CommonCategoryDetail.vue
  11. 25 3
      src/pages/article/data/CommonCategoryDetailIntroBlocks.vue
  12. 30 2
      src/pages/article/data/CommonCategoryDynamicData.ts
  13. 6 2
      src/pages/article/data/CommonCategoryListBlock.vue
  14. 513 37
      src/pages/article/data/DefaultCategory.json
  15. 4 0
      src/pages/article/data/defines/Details.ts
  16. 2 0
      src/pages/article/data/defines/List.ts
  17. 42 10
      src/pages/article/data/editor/MiniProgramEditor.vue
  18. 33 0
      src/pages/article/data/editor/components/DynamicDataEditor.vue
  19. 48 17
      src/pages/article/data/editor/components/LinkPathEditor.vue
  20. 238 0
      src/pages/article/data/editor/editors/DetailPropsEditor.vue
  21. 12 0
      src/pages/article/data/editor/subpart/EditorPreview.vue
  22. 10 0
      src/pages/article/data/editor/subpart/PropsEditorTree.vue
  23. 1 5
      src/pages/blocks/StatsBlock.vue
  24. 1 1
      src/pages/home/index.vue
  25. 0 192
      src/pages/inhert/inheritor/details.vue
  26. 1 1
      src/pages/inhert/inheritor/list.vue
  27. 2 2
      src/pages/inhert/map/index.vue
  28. 1 1
      src/pages/inhert/old/list.vue
  29. 0 18
      src/pages/inhert/product/details.vue
  30. 0 18
      src/pages/inhert/seminar/details.vue
  31. 0 18
      src/pages/inhert/songs/details.vue
  32. 0 67
      src/pages/introduction/custom/list.vue
  33. 0 112
      src/pages/introduction/news.vue
  34. 3 3
      src/pages/user/collect/index.vue

+ 1 - 0
src/App.vue

@@ -39,6 +39,7 @@ configTheme(false, (theme, darkTheme) => {
   theme.colorConfigs.default.primary = '#d9492e';
   theme.colorConfigs.default.primary = '#d9492e';
   theme.colorConfigs.pressed.primary = '#882d1d';
   theme.colorConfigs.pressed.primary = '#882d1d';
   theme.colorConfigs.background.primary = '#ffcfc6';
   theme.colorConfigs.background.primary = '#ffcfc6';
+  theme.colorConfigs.background.warning = '#f5ebe0';
   theme.colorConfigs.background.page = '#f6f2e7';
   theme.colorConfigs.background.page = '#f6f2e7';
   return [theme, darkTheme];
   return [theme, darkTheme];
 });
 });

+ 7 - 0
src/api/CommonContent.ts

@@ -279,6 +279,12 @@ export class GetContentDetailItem extends DataModel<GetContentDetailItem> {
       if (this.publishVideo) {
       if (this.publishVideo) {
         this.video = this.publishVideo
         this.video = this.publishVideo
       }
       }
+      if (this.associationMeList) {
+        this.associationMeFirstTitle = this.associationMeList[0]?.title || ''
+      }
+      if (this.otherLevel) {
+        this.otherLevelCount = this.otherLevel.length
+      }
     }
     }
   }
   }
 
 
@@ -318,6 +324,7 @@ export class GetContentDetailItem extends DataModel<GetContentDetailItem> {
     image: string,
     image: string,
     thumbnail: string,
     thumbnail: string,
   }[];
   }[];
+  associationMeFirstTitle = '';
   otherLevel : GetContentDetailItem[] = [];
   otherLevel : GetContentDetailItem[] = [];
 }
 }
 
 

+ 0 - 43
src/pages.json

@@ -21,13 +21,6 @@
       }
       }
     },
     },
     {
     {
-      "path": "pages/introduction/custom/list",
-      "style": {
-        "navigationBarTitleText": "闽南民俗",
-        "enablePullDownRefresh": true
-      }
-    },
-    {
       "path": "pages/introduction/character/details",
       "path": "pages/introduction/character/details",
       "style": {
       "style": {
         "navigationBarTitleText": "历史人物详情"
         "navigationBarTitleText": "历史人物详情"
@@ -76,24 +69,6 @@
       }
       }
     },
     },
     {
     {
-      "path": "pages/inhert/artifact/details",
-      "style": {
-        "navigationBarTitleText": "文物详情"
-      }
-    },
-    {
-      "path": "pages/inhert/intangible/details",
-      "style": {
-        "navigationBarTitleText": "非遗项目详情"
-      }
-    },
-    {
-      "path": "pages/inhert/product/details",
-      "style": {
-        "navigationBarTitleText": "非遗作品详情"
-      }
-    },
-    {
       "path": "pages/inhert/unit/list",
       "path": "pages/inhert/unit/list",
       "style": {
       "style": {
         "navigationBarTitleText": "保护单位",
         "navigationBarTitleText": "保护单位",
@@ -108,18 +83,6 @@
       }
       }
     },
     },
     {
     {
-      "path": "pages/inhert/inheritor/details",
-      "style": {
-        "navigationBarTitleText": "传承人详情"
-      }
-    },
-    {
-      "path": "pages/inhert/seminar/details",
-      "style": {
-        "navigationBarTitleText": "传习所详情"
-      }
-    },
-    {
       "path": "pages/inhert/old/list",
       "path": "pages/inhert/old/list",
       "style": {
       "style": {
         "navigationBarTitleText": "重要相关老字号",
         "navigationBarTitleText": "重要相关老字号",
@@ -139,12 +102,6 @@
       }
       }
     },
     },
     {
     {
-      "path": "pages/inhert/songs/details",
-      "style": {
-        "navigationBarTitleText": "详情"
-      }
-    },
-    {
       "path": "pages/inhert/village/list",
       "path": "pages/inhert/village/list",
       "style": {
       "style": {
         "navigationBarTitleText": "村落列表",
         "navigationBarTitleText": "村落列表",

+ 0 - 6
src/pages/article/common/DetailTabPage.ts

@@ -2,11 +2,6 @@ import type { GetContentDetailItem } from "@/api/CommonContent";
 import type { TabControlItem } from "@/common/composeabe/TabControl";
 import type { TabControlItem } from "@/common/composeabe/TabControl";
 import type { Ref } from "vue";
 import type { Ref } from "vue";
 
 
-export const TAB_ID_INTRO = 0;
-export const TAB_ID_IMAGES = 1;
-export const TAB_ID_VIDEO = 2;
-export const TAB_ID_AUDIO = 3;
-
 export interface DetailTabPageTabsArray {
 export interface DetailTabPageTabsArray {
   tabsArray: Ref<TabControlItem[]>,
   tabsArray: Ref<TabControlItem[]>,
   getTabById(id: number): TabControlItem | undefined;
   getTabById(id: number): TabControlItem | undefined;
@@ -19,7 +14,6 @@ export interface DetailTabPageProps {
   ) => Promise<GetContentDetailItem>,
   ) => Promise<GetContentDetailItem>,
   extraTabs?: TabControlItem[],
   extraTabs?: TabControlItem[],
   showHead?: boolean,
   showHead?: boolean,
-  hasInternalTabs?: boolean,
   overrideInternalTabsName?: undefined|{
   overrideInternalTabsName?: undefined|{
     [key: number]: string,
     [key: number]: string,
   },
   },

+ 4 - 81
src/pages/article/common/DetailTabPage.vue

@@ -35,58 +35,12 @@
               :autoScroll="true"
               :autoScroll="true"
               :autoItemWidth="false"
               :autoItemWidth="false"
               :defaultIndicatorWidth="130"
               :defaultIndicatorWidth="130"
+              :width="730"
               class="top-tab"
               class="top-tab"
             />
             />
           </view>
           </view>
-
           <view class="d-flex flex-col radius-l bg-light p-25 mt-3" style="min-height:70vh">
           <view class="d-flex flex-col radius-l bg-light p-25 mt-3" style="min-height:70vh">
-            <!-- 简介 -->
-            <template v-if="hasInternalTabs && tabCurrentId == TAB_ID_INTRO">
-              <Parse
-                v-if="loader.content.value.intro"
-                :content="(loader.content.value.intro as string)"
-              />
-              <Parse
-                v-if="loader.content.value.content"
-                :content="loader.content.value.content"
-              />
-              <text v-if="emptyContent">暂无简介</text>
-              <text v-if="loader.content.value.from" class="size-s color-text-content-second mr-2 ">以上内容摘自 {{ loader.content.value.from }}</text>
-            </template>
-            <!-- 图片 -->
-            <template v-else-if="hasInternalTabs && tabCurrentId == TAB_ID_IMAGES">
-              <slot name="imagesPrefix" />
-              <ImageGrid
-                :images="loader.content.value.images"
-                :rowCount="2"
-                :preview="true"
-                imageHeight="200rpx"
-              />
-            </template>
-            <!-- 视频 -->
-            <template v-else-if="hasInternalTabs && tabCurrentId == TAB_ID_VIDEO">
-              <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="hasInternalTabs && tabCurrentId == TAB_ID_AUDIO">
-              <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" />
+            <slot name="extraTabs" :tabCurrentId="tabCurrentId" :content="loader.content.value" />
           </view>
           </view>
           <ContentNote />
           <ContentNote />
         </view>
         </view>
@@ -104,26 +58,20 @@ import { computed } from "vue";
 import { useSimplePageContentLoader } from "@/common/composeabe/SimplePageContentLoader";
 import { useSimplePageContentLoader } from "@/common/composeabe/SimplePageContentLoader";
 import { useLoadQuerys } from "@/common/composeabe/LoadQuerys";
 import { useLoadQuerys } from "@/common/composeabe/LoadQuerys";
 import { useTabControl } from "@/common/composeabe/TabControl";
 import { useTabControl } from "@/common/composeabe/TabControl";
-import { requireNotNull } from "@imengyu/imengyu-utils";
 import SimplePageContentLoader from "@/common/components/SimplePageContentLoader.vue";
 import SimplePageContentLoader from "@/common/components/SimplePageContentLoader.vue";
-import ImageGrid from "@/pages/parts/ImageGrid.vue";
 import ImageSwiper from "@/pages/parts/ImageSwiper.vue";
 import ImageSwiper from "@/pages/parts/ImageSwiper.vue";
 import ContentNote from "@/pages/parts/ContentNote.vue";
 import ContentNote from "@/pages/parts/ContentNote.vue";
-import Parse from "@/components/display/parse/Parse.vue";
 import Tabs from "@/components/nav/Tabs.vue";
 import Tabs from "@/components/nav/Tabs.vue";
 import LikeFooter from "@/pages/parts/LikeFooter.vue";
 import LikeFooter from "@/pages/parts/LikeFooter.vue";
 import ArticleCorrect from "@/pages/parts/ArticleCorrect.vue";
 import ArticleCorrect from "@/pages/parts/ArticleCorrect.vue";
 import type { GetContentDetailItem } from "@/api/CommonContent";
 import type { GetContentDetailItem } from "@/api/CommonContent";
-import { TAB_ID_AUDIO, TAB_ID_IMAGES, TAB_ID_INTRO, TAB_ID_VIDEO, type DetailTabPageProps, type DetailTabPageTabsArray } from "./DetailTabPage";
+import type { DetailTabPageProps, DetailTabPageTabsArray } from "./DetailTabPage";
 
 
 const props = withDefaults(defineProps<DetailTabPageProps>(), {
 const props = withDefaults(defineProps<DetailTabPageProps>(), {
   extraTabs: () => [],
   extraTabs: () => [],
   showHead: true,
   showHead: true,
-  hasInternalTabs: true,
 })
 })
 
 
-const hasInternalTabs = computed(() => props.hasInternalTabs !== false)
-
 const emit = defineEmits([
 const emit = defineEmits([
   "tabChange",
   "tabChange",
   "loaded"
   "loaded"
@@ -143,9 +91,6 @@ const loader = useSimplePageContentLoader<
     throw new Error("!props.load");
     throw new Error("!props.load");
 
 
   const d = await props.load(params.id, tabsArrayObject);
   const d = await props.load(params.id, tabsArrayObject);
-  requireNotNull(tabsArrayObject.getTabById(TAB_ID_IMAGES)).visible = Boolean(d.images && d.images.length > 1);
-  requireNotNull(tabsArrayObject.getTabById(TAB_ID_VIDEO)).visible = Boolean(d.video);
-  requireNotNull(tabsArrayObject.getTabById(TAB_ID_AUDIO)).visible = Boolean(d.audio);
 
 
   if (d.title)
   if (d.title)
     uni.setNavigationBarTitle({ title: d.title });
     uni.setNavigationBarTitle({ title: d.title });
@@ -167,29 +112,7 @@ const {
   tabsArray,
   tabsArray,
   tabs,
   tabs,
 } = useTabControl({
 } = useTabControl({
-  tabs: [
-    {
-      id: TAB_ID_INTRO,
-      text: props.overrideInternalTabsName?.[TAB_ID_INTRO] || '简介',
-      visible: true,
-    },
-    {
-      id: TAB_ID_IMAGES,
-      text: props.overrideInternalTabsName?.[TAB_ID_IMAGES] || '图片',
-      visible: true,
-    },
-    {
-      id: TAB_ID_VIDEO,
-      text: props.overrideInternalTabsName?.[TAB_ID_VIDEO] || '视频',
-      visible: true,
-    },
-    {
-      id: TAB_ID_AUDIO,
-      text: props.overrideInternalTabsName?.[TAB_ID_AUDIO] || '音频',
-      visible: true,
-    },
-    ...props.extraTabs,
-  ],
+  tabs: props.extraTabs,
   onTabChange(a, b) {
   onTabChange(a, b) {
     emit("tabChange", a, b);
     emit("tabChange", a, b);
   },
   },

+ 3 - 3
src/pages/article/common/DetailsCommon.vue

@@ -47,7 +47,7 @@
           :showSearch="false"
           :showSearch="false"
           :hasBg="false"
           :hasBg="false"
           :load="(page: number, pageSize: number) => loadSubList(page, pageSize, content, 'ichSitesList')"
           :load="(page: number, pageSize: number) => loadSubList(page, pageSize, content, 'ichSitesList')"
-          detailsPage="/pages/inhert/seminar/details"
+          detailsPage="/pages/article/data/details?pageConfigName=seminar-details"
           :detailsParams="{
           :detailsParams="{
             mainBodyColumnId: SeminarContent.mainBodyColumnId,
             mainBodyColumnId: SeminarContent.mainBodyColumnId,
             modelId: SeminarContent.modelId,
             modelId: SeminarContent.modelId,
@@ -60,7 +60,7 @@
           :showSearch="false"
           :showSearch="false"
           :hasBg="false"
           :hasBg="false"
           :load="(page: number, pageSize: number) => loadSubList(page, pageSize, content, 'inheritorsList')"
           :load="(page: number, pageSize: number) => loadSubList(page, pageSize, content, 'inheritorsList')"
-          detailsPage="/pages/inhert/inheritor/details"
+          detailsPage="/pages/article/data/details?pageConfigName=inheritor-details"
           :detailsParams="{
           :detailsParams="{
             mainBodyColumnId: InheritorContent.mainBodyColumnId,
             mainBodyColumnId: InheritorContent.mainBodyColumnId,
             modelId: InheritorContent.modelId,
             modelId: InheritorContent.modelId,
@@ -79,7 +79,7 @@
           :showSearch="false"
           :showSearch="false"
           :hasBg="false"
           :hasBg="false"
           :load="(page: number, pageSize: number) => loadSubList(page, pageSize, content, 'worksList')"
           :load="(page: number, pageSize: number) => loadSubList(page, pageSize, content, 'worksList')"
-          :detailsPage="`/pages/inhert/product/details`"
+          :detailsPage="`/pages/article/data/details?pageConfigName=product-details`"
           :detailsParams="{
           :detailsParams="{
             mainBodyColumnId: ProductsContent.mainBodyColumnId,
             mainBodyColumnId: ProductsContent.mainBodyColumnId,
             modelId: ProductsContent.modelId,
             modelId: ProductsContent.modelId,

+ 2 - 1
src/pages/article/data/CommonCategoryBlocks.ts

@@ -1,11 +1,12 @@
 import type { CommonContentApi } from "@/api/CommonContent";
 import type { CommonContentApi } from "@/api/CommonContent";
 import type { IHomeCommonCategoryBlock, HomeCommonCategoryBlockProps } from "../common/CommonContent";
 import type { IHomeCommonCategoryBlock, HomeCommonCategoryBlockProps } from "../common/CommonContent";
 import type { IHomeCommonCategoryListTabNestCategoryItemDefine } from "./CommonCategoryDefine";
 import type { IHomeCommonCategoryListTabNestCategoryItemDefine } from "./CommonCategoryDefine";
+import type { IHomeCommonCategoryDynamicDataDetailContent } from "./CommonCategoryDynamicData";
 
 
 export interface CategoryDefine extends Omit<IHomeCommonCategoryListTabNestCategoryItemDefine, 'type'> {
 export interface CategoryDefine extends Omit<IHomeCommonCategoryListTabNestCategoryItemDefine, 'type'> {
   title: string;
   title: string;
   showTitle: boolean;
   showTitle: boolean;
-  content: CommonContentApi|IHomeCommonCategoryBlock|HomeCommonCategoryBlockProps|null;
+  content: CommonContentApi|IHomeCommonCategoryBlock|HomeCommonCategoryBlockProps|IHomeCommonCategoryDynamicDataDetailContent|null;
   type?: 'article'|'large-image2'|'horizontal-large'|'large-image'|'large-grid2'|'small-grid2'|'simple-text'
   type?: 'article'|'large-image2'|'horizontal-large'|'large-image'|'large-grid2'|'small-grid2'|'simple-text'
     |'default'
     |'default'
     |'CalendarBlock'|'StatsBlock'|'MapBlock'|''
     |'CalendarBlock'|'StatsBlock'|'MapBlock'|''

+ 52 - 28
src/pages/article/data/CommonCategoryBlocks.vue

@@ -164,7 +164,7 @@
 
 
 <script setup lang="ts">;
 <script setup lang="ts">;
 import { computed, onMounted, watch, type PropType } from 'vue';
 import { computed, onMounted, watch, type PropType } from 'vue';
-import { CommonContentApi, GetContentListItem, GetContentListParams } from '@/api/CommonContent';
+import CommonContent, { CommonContentApi, GetContentListItem, GetContentListParams } from '@/api/CommonContent';
 import { navCommonDetail, navCommonList, resolveCommonContentGetPageDetailUrlAuto, resolveCommonContentSolveProps, useHomeCommonCategoryBlock, type HomeCommonCategoryBlockProps, type IHomeCommonCategoryBlock } from '../common/CommonContent';
 import { navCommonDetail, navCommonList, resolveCommonContentGetPageDetailUrlAuto, resolveCommonContentSolveProps, useHomeCommonCategoryBlock, type HomeCommonCategoryBlockProps, type IHomeCommonCategoryBlock } from '../common/CommonContent';
 import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
 import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
 import { navTo } from '@/components/utils/PageAction';
 import { navTo } from '@/components/utils/PageAction';
@@ -181,6 +181,7 @@ import MapCategoryBlock from '@/pages/blocks/MapBlock.vue';
 import Image from '@/components/basic/Image.vue';
 import Image from '@/components/basic/Image.vue';
 import AppCofig from '@/common/config/AppCofig';
 import AppCofig from '@/common/config/AppCofig';
 import type { CategoryDefine } from './CommonCategoryBlocks';
 import type { CategoryDefine } from './CommonCategoryBlocks';
+import type { IHomeCommonCategoryDynamicDataDetailContent } from './CommonCategoryDynamicData';
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -203,25 +204,27 @@ const categoryDatas = computed(() => props.categoryDefine.map(item => {
       },
       },
       data: null,
       data: null,
     };
     };
+
+  const defaultDetailsPageHandler = (dataItem: GetContentListItem) => {
+    const id = dataItem.id;
+    const content = item.content as CommonContentApi;
+    if (item.detailsPage) {
+      if (item.detailsPage === 'byContent')
+        navTo(resolveCommonContentGetPageDetailUrlAuto(dataItem), { id });
+      else
+        navTo(item.detailsPage, { id });
+    } else {
+      navCommonDetail({
+        id,
+        mainBodyColumnId: content.mainBodyColumnId,
+        modelId: content.modelId,
+      })
+    }
+  };
   if (item.content instanceof CommonContentApi) {
   if (item.content instanceof CommonContentApi) {
     return {
     return {
       ...item,
       ...item,
-      detailsPage: (dataItem: GetContentListItem) => {
-        const id = dataItem.id;
-        const content = item.content as CommonContentApi;
-        if (item.detailsPage) {
-          if (item.detailsPage === 'byContent')
-            navTo(resolveCommonContentGetPageDetailUrlAuto(dataItem), { id });
-          else
-            navTo(item.detailsPage, { id });
-        } else {
-          navCommonDetail({
-            id,
-            mainBodyColumnId: content.mainBodyColumnId,
-            modelId: content.modelId,
-          })
-        }
-      },
+      detailsPage: defaultDetailsPageHandler,
       morePage: () => {
       morePage: () => {
         const content = item.content as CommonContentApi;
         const content = item.content as CommonContentApi;
         if (item.morePage) {
         if (item.morePage) {
@@ -243,17 +246,38 @@ const categoryDatas = computed(() => props.categoryDefine.map(item => {
       }, false)
       }, false)
     }
     }
   } else {
   } else {
-    const block = item.content.type === 'CommonCategoryBlock' ? 
-      item.content : 
-      useHomeCommonCategoryBlock({
-        ...item.content,
-        dataSolve: item.dataSolve ?? [],
-      }, false);
-    return {
-      ...item,
-      detailsPage: block.goDetail,
-      morePage: block.goList,
-      data: block.loader,
+    switch (item.content.type) {
+      case 'CommonCategoryBlock':
+        return {
+          ...item,
+          detailsPage: item.content.goDetail,
+          morePage: item.content.goList,
+          data: item.content.loader,
+        }
+      case 'detailContent':
+        return {
+          ...item,
+          detailsPage: defaultDetailsPageHandler,
+          morePage: () => {},
+          data: useSimpleDataLoader(async () => {
+            console.log(item);
+            const data = item.data as IHomeCommonCategoryDynamicDataDetailContent;
+            const res = await CommonContent.getContentDetail(data.params!.id, undefined, data.params!.modelId || undefined);
+            return resolveCommonContentSolveProps([res as any], item.dataSolve || []);;
+          }, false),
+        }
+      default: {
+        const block = useHomeCommonCategoryBlock({
+          ...item.content,
+          dataSolve: item.dataSolve ?? [],
+        }, false);
+        return {
+          ...item,
+          detailsPage: block.goDetail,
+          morePage: block.goList,
+          data: block.loader,
+        }
+      }
     }
     }
   }
   }
 }));
 }));

+ 1 - 0
src/pages/article/data/CommonCategoryDefine.ts

@@ -3,6 +3,7 @@ import type { IHomeCommonCategoryHomeDefine } from "./defines/Home";
 import type { IHomeCommonCategoryListDefine } from "./defines/List";
 import type { IHomeCommonCategoryListDefine } from "./defines/List";
 
 
 export * from "./defines/Blocks";
 export * from "./defines/Blocks";
+export * from "./defines/Details";
 export * from "./defines/List";
 export * from "./defines/List";
 export * from "./defines/Home";
 export * from "./defines/Home";
 
 

+ 40 - 11
src/pages/article/data/CommonCategoryDetail.vue

@@ -10,7 +10,7 @@
       v-else
       v-else
       ref="pageRef"
       ref="pageRef"
       v-bind="$attrs"
       v-bind="$attrs"
-      :hasInternalTabs="false"
+      :noInternalTabs="true"
       :load="load"
       :load="load"
       :extraTabs="tabRenderDefinesArray"
       :extraTabs="tabRenderDefinesArray"
       @loaded="onLoaded"
       @loaded="onLoaded"
@@ -31,6 +31,9 @@
         </template>
         </template>
         <template v-else-if="tabRenderDefines[tabCurrentId].type === 'images'">
         <template v-else-if="tabRenderDefines[tabCurrentId].type === 'images'">
           <!-- 图片 -->
           <!-- 图片 -->
+          <view v-if="tabRenderDefines[tabCurrentId].prefix" class="d-flex flex-row justify-center align-center mt-2 mb-2">
+            <text class="size-s font-bold color-text-content">{{ tabRenderDefines[tabCurrentId].prefix }}</text>
+          </view>
           <ImageGrid
           <ImageGrid
             :images="content.images"
             :images="content.images"
             :rowCount="2"
             :rowCount="2"
@@ -73,6 +76,7 @@
             :pageQuerys="pageQuerys"
             :pageQuerys="pageQuerys"
             :parentData="content"
             :parentData="content"
             :hasPadding="false"
             :hasPadding="false"
+            :hasBg="false"
             @error="errorMessage = $event"
             @error="errorMessage = $event"
           />
           />
         </template>
         </template>
@@ -86,6 +90,15 @@
           <!-- 嵌套分类 -->
           <!-- 嵌套分类 -->
           <CommonCategoryBlocks :categoryDefine="tabRenderDefines[tabCurrentId].categoryDefine" />
           <CommonCategoryBlocks :categoryDefine="tabRenderDefines[tabCurrentId].categoryDefine" />
         </template>
         </template>
+        <template v-else-if="tabRenderDefines[tabCurrentId].key === 'vr'">
+          <!-- VR参观 -->
+          <view class="d-flex flex-row justify-center p-5">
+            <Button @click="handleGoToVr(content.vr as string)">
+              <text class="iconfont icon-go"></text>
+              点击参观
+            </Button>
+          </view>
+        </template>
         <template v-else>
         <template v-else>
           <CommonCategoryDetailContentBlocks
           <CommonCategoryDetailContentBlocks
             :define="tabRenderDefines[tabCurrentId]"
             :define="tabRenderDefines[tabCurrentId]"
@@ -137,6 +150,7 @@ import CommonContent from '@/api/CommonContent';
 import CommonCategoryDetailContentBlocks from './CommonCategoryDetailContentBlocks.vue';
 import CommonCategoryDetailContentBlocks from './CommonCategoryDetailContentBlocks.vue';
 import ImageGrid from '@/pages/parts/ImageGrid.vue';
 import ImageGrid from '@/pages/parts/ImageGrid.vue';
 import CommonCategoryListBlock from './CommonCategoryListBlock.vue';
 import CommonCategoryListBlock from './CommonCategoryListBlock.vue';
+import { navTo } from '@/components/utils/PageAction';
 
 
 export interface CommonCategoryDetailProps extends DetailTabPageProps {
 export interface CommonCategoryDetailProps extends DetailTabPageProps {
   /**
   /**
@@ -145,6 +159,7 @@ export interface CommonCategoryDetailProps extends DetailTabPageProps {
   introBlockDescs?: {
   introBlockDescs?: {
     label: string;
     label: string;
     key: string;
     key: string;
+    map?: Record<string|number, string>;
   }[];
   }[];
   /**
   /**
    * 简介下方块
    * 简介下方块
@@ -152,9 +167,8 @@ export interface CommonCategoryDetailProps extends DetailTabPageProps {
   introBlocks?: CommonCategoryDetailIntroBlocksDesc[];
   introBlocks?: CommonCategoryDetailIntroBlocksDesc[];
 }
 }
 export interface CommonCategoryDetailIntroBlocksDesc {
 export interface CommonCategoryDetailIntroBlocksDesc {
-  label?: string;
   type: string;
   type: string;
-  key: string;
+  props?: Record<string, any>;
 }
 }
 export type RenderTabDefine = IHomeCommonCategoryDetailTabItemDefine & {
 export type RenderTabDefine = IHomeCommonCategoryDetailTabItemDefine & {
   id: number;
   id: number;
@@ -253,9 +267,7 @@ async function loadPageConfig() {
     if (hasNestCategory)
     if (hasNestCategory)
       await waitTimeOut(100);
       await waitTimeOut(100);
     loadState.value = true;
     loadState.value = true;
-
     await waitTimeOut(100);
     await waitTimeOut(100);
-
     pageRef.value?.load(props.pageQuerys);
     pageRef.value?.load(props.pageQuerys);
   } catch (error) {
   } catch (error) {
     console.error(error);
     console.error(error);
@@ -270,16 +282,23 @@ onMounted(loadPageConfig);
 const content = ref<any>();
 const content = ref<any>();
 const descItems = computed(() => (
 const descItems = computed(() => (
   currentCommonCategoryContentDefine.value?.props.introBlockDescs || [])
   currentCommonCategoryContentDefine.value?.props.introBlockDescs || [])
-    .map((item) => ({
-      ...item,
-      value: content.value?.[item.key] || '',
-    }))
+    .map((item) => {
+      let value = content.value?.[item.key] || '';
+      if (item.map)
+        value = item.map[value] || '';
+      return {
+        ...item,
+        value,
+      }
+    })
 );
 );
 
 
 function onLoaded(d: any) {
 function onLoaded(d: any) {
   content.value = d;
   content.value = d;
 }
 }
 async function load(id: number, tabsArray: DetailTabPageTabsArray) {
 async function load(id: number, tabsArray: DetailTabPageTabsArray) {
+  if (isNaN(id) || id <= 0)
+    throw new Error("请输入ID。如果正在测试,可在后台复制ID");
   const d = await CommonContent.getContentDetail(
   const d = await CommonContent.getContentDetail(
     id, 
     id, 
     undefined, 
     undefined, 
@@ -288,15 +307,25 @@ async function load(id: number, tabsArray: DetailTabPageTabsArray) {
   for (const tab of tabRenderDefinesArray.value) {
   for (const tab of tabRenderDefinesArray.value) {
     const v = d[tab.key];
     const v = d[tab.key];
     let check = true
     let check = true
-    if (!v)
+    if (['intro','map'].includes(tab.type))
+      check = true;
+    else if (tab.type === 'audio')
+      check = Boolean(d.audio);
+    else if (tab.type === 'video')
+      check = Boolean(d.video);
+    else if (tab.type === 'images')
+      check = Boolean(d.images) && (d.images as string[]).length > 0;
+    else if (!v)
       check = false;
       check = false;
     else if (Array.isArray(v))
     else if (Array.isArray(v))
       check = (v as any[]).length > 0;
       check = (v as any[]).length > 0;
-
     tabsArray.getTabById(tab.id)!.visible = tab.visible !== false && check;
     tabsArray.getTabById(tab.id)!.visible = tab.visible !== false && check;
   }
   }
   return d;
   return d;
 }
 }
+function handleGoToVr(vr: string) {
+  navTo('/pages/article/web/ewebview', { url: vr })
+}
 
 
 defineExpose({
 defineExpose({
   getPageShareData() {
   getPageShareData() {

+ 25 - 3
src/pages/article/data/CommonCategoryDetailIntroBlocks.vue

@@ -7,7 +7,7 @@
         v-for="(item, k) in content.otherLevel"
         v-for="(item, k) in content.otherLevel"
         :key="k"
         :key="k"
         class="d-flex flex-row align-center justify-between p-3 radius-base bg-light"
         class="d-flex flex-row align-center justify-between p-3 radius-base bg-light"
-        @click="navTo('/pages/inhert/intangible/details', {
+        @click="navTo('/pages/article/data/details?pageConfigName=intangible-details', {
           id: item.id,
           id: item.id,
         })"
         })"
       >
       >
@@ -28,16 +28,30 @@
         <text class="iconfont icon-arrow-right"></text>
         <text class="iconfont icon-arrow-right"></text>
       </view>
       </view>
     </view>
     </view>
+    <!-- 去这里导航按钮 -->
+    <FlexRow v-else-if="item.type == 'NavTo' && content.address" justify="space-between" align="center">
+      <FlexRow :gap="10" :radius="20" :padding="10" align="center" backgroundColor="background.warning">
+        <Icon icon="map" />
+        <text class="text-lines-2">{{ content.address }}</text>
+      </FlexRow>
+      <Button icon="navigation" type="text" @click="openLocation">     
+        去这里
+      </Button>
+    </FlexRow>
   </template>
   </template>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { type PropType } from 'vue';
 import { type PropType } from 'vue';
 import type { CommonCategoryDetailIntroBlocksDesc } from './CommonCategoryDetail.vue';
 import type { CommonCategoryDetailIntroBlocksDesc } from './CommonCategoryDetail.vue';
-import { navTo } from '@/components/utils/PageAction';
 import { StringUtils } from '@imengyu/imengyu-utils';
 import { StringUtils } from '@imengyu/imengyu-utils';
+import Tag from '@/components/display/Tag.vue';
+import FlexRow from '@/components/layout/FlexRow.vue';
+import Button from '@/components/basic/Button.vue';
+import Icon from '@/components/basic/Icon.vue';
+import { navTo } from '@/components/utils/PageAction';
 
 
-defineProps({
+const props = defineProps({
   introBlocks: {
   introBlocks: {
     type: Array as PropType<CommonCategoryDetailIntroBlocksDesc[]>,
     type: Array as PropType<CommonCategoryDetailIntroBlocksDesc[]>,
     default: () => [],
     default: () => [],
@@ -47,4 +61,12 @@ defineProps({
     default: () => ({}),
     default: () => ({}),
   },
   },
 })
 })
+
+function openLocation() {
+  uni.openLocation({
+    latitude: Number(props.content.latitude),
+    longitude: Number(props.content.longitude),
+    name: props.content.address,
+  });
+}
 </script>
 </script>

+ 30 - 2
src/pages/article/data/CommonCategoryDynamicData.ts

@@ -59,6 +59,26 @@ export interface IHomeCommonCategoryDynamicDataCommonContent {
   otherParams?: Record<string, any>,
   otherParams?: Record<string, any>,
 }
 }
 /**
 /**
+ * 默认列表动态数据接口定义 - 通用详情内容
+ */
+export interface IHomeCommonCategoryDynamicDataDetailContent {
+  type: 'detailContent',
+  params: {
+    /**
+     * 内容ID
+     */
+    id: number,
+    /**
+     * 模型ID
+     */
+    modelId: number,
+  },
+  /**
+   * 其他参数
+   */
+  otherParams?: Record<string, any>,
+}
+/**
  * 默认列表动态数据接口定义 - 已序列化的预制接口
  * 默认列表动态数据接口定义 - 已序列化的预制接口
  */
  */
 export interface IHomeCommonCategoryDynamicDataSerializedApi {
 export interface IHomeCommonCategoryDynamicDataSerializedApi {
@@ -99,7 +119,8 @@ export interface IHomeCommonCategoryDynamicDataParentKey {
 export type IHomeCommonCategoryDynamicData = IHomeCommonCategoryDynamicDataCommonContent 
 export type IHomeCommonCategoryDynamicData = IHomeCommonCategoryDynamicDataCommonContent 
   | IHomeCommonCategoryDynamicDataSerializedApi 
   | IHomeCommonCategoryDynamicDataSerializedApi 
   | IHomeCommonCategoryDynamicDataRequest
   | IHomeCommonCategoryDynamicDataRequest
-  | IHomeCommonCategoryDynamicDataParentKey;
+  | IHomeCommonCategoryDynamicDataParentKey
+  | IHomeCommonCategoryDynamicDataDetailContent;
 
 
 /**
 /**
  * 序列化接口映射表
  * 序列化接口映射表
@@ -164,10 +185,17 @@ export async function doLoadDynamicListData(
           ...CommonCategorDynamicDropDownValuesToParams(dropDownValues, dropdownDefines || []),
           ...CommonCategorDynamicDropDownValuesToParams(dropDownValues, dropdownDefines || []),
         }) 
         }) 
       , page, pageSize);
       , page, pageSize);
+    case 'detailContent': {
+      const res = await CommonContent.getContentDetail(item.params.id, undefined, item.params.modelId || undefined);
+      return {
+        list: page === 1 ? [res] : [],
+        total: 1,
+      };
+    }
     case 'parentKey': {
     case 'parentKey': {
       if (!parentData)
       if (!parentData)
         throw new Error(`此处不允许加载父级数据`);
         throw new Error(`此处不允许加载父级数据`);
-      const arr = parentData?.[item.key] || [];
+      const arr = page === 1 ? parentData?.[item.key] || [] : [];
       return {
       return {
         list: arr,
         list: arr,
         total: arr.length,
         total: arr.length,

+ 6 - 2
src/pages/article/data/CommonCategoryListBlock.vue

@@ -2,6 +2,8 @@
   <CommonListPage
   <CommonListPage
     v-if="currentCommonCategoryDefine"
     v-if="currentCommonCategoryDefine"
     :startTabIndex="pageStartTab"
     :startTabIndex="pageStartTab"
+    :hasPadding="hasPadding"
+    :hasBg="hasBg"
     v-bind="currentCommonCategoryDefine.content.props as any || undefined"
     v-bind="currentCommonCategoryDefine.content.props as any || undefined"
     :title="currentCommonCategoryDefine.title || undefined"
     :title="currentCommonCategoryDefine.title || undefined"
     :load="loadData"
     :load="loadData"
@@ -9,7 +11,6 @@
     :dropDownNames="dropdownNames"
     :dropDownNames="dropdownNames"
     :showListTabIds="showListTabIds"
     :showListTabIds="showListTabIds"
     :detailsPage="detailsPage"
     :detailsPage="detailsPage"
-    :hasPadding="hasPadding"
   >
   >
     <template #list="{ tabId }">
     <template #list="{ tabId }">
       <CommonCategoryBlocks
       <CommonCategoryBlocks
@@ -48,9 +49,12 @@ const props = withDefaults(defineProps<{
   pageQuerys?: Record<string, any>,
   pageQuerys?: Record<string, any>,
   parentData?: any,
   parentData?: any,
   hasPadding?: boolean,
   hasPadding?: boolean,
+  hasBg?: boolean,
 }>(), {
 }>(), {
   pageStartTab: 0,
   pageStartTab: 0,
   pageQuerys: () => ({}),
   pageQuerys: () => ({}),
+  hasPadding: true,
+  hasBg: true,
 });
 });
 const emit = defineEmits<{
 const emit = defineEmits<{
   (e: 'error', error: any): void;
   (e: 'error', error: any): void;
@@ -165,7 +169,7 @@ const showListTabIds = computed(() => {
 })
 })
 const tabs = computed<CommonListPageProps['tabs']>(() => {
 const tabs = computed<CommonListPageProps['tabs']>(() => {
   const define = tabDefines.value;
   const define = tabDefines.value;
-  return define.map((item, i) => {
+  return define.filter((item) => item.visible !== false).map((item, i) => {
     switch (item.type) {
     switch (item.type) {
       default:
       default:
       case 'list':
       case 'list':

+ 513 - 37
src/pages/article/data/DefaultCategory.json

@@ -12,7 +12,7 @@
           "homeButtons": [
           "homeButtons": [
             {
             {
               "title": "常识一点通",
               "title": "常识一点通",
-              "icon": "https://mncdn.wenlvti.net/app_static/minnan/images/home/Button1.png",
+              "icon": "https://mncdn.wenlvti.net/app_static/minnan/images/home/Button11.png",
               "size": 50,
               "size": 50,
               "link": [
               "link": [
                 "/pages/article/data/list",
                 "/pages/article/data/list",
@@ -24,7 +24,7 @@
             },
             },
             {
             {
               "title": "闽南新鲜事",
               "title": "闽南新鲜事",
-              "icon": "https://mncdn.wenlvti.net/app_static/minnan/images/home/Button2.png",
+              "icon": "https://mncdn.wenlvti.net/app_static/minnan/images/home/Button12.png",
               "size": 50,
               "size": 50,
               "link": [
               "link": [
                 "/pages/article/data/list",
                 "/pages/article/data/list",
@@ -36,7 +36,7 @@
             },
             },
             {
             {
               "title": "遗产报你知",
               "title": "遗产报你知",
-              "icon": "https://mncdn.wenlvti.net/app_static/minnan/images/home/Button3.png",
+              "icon": "https://mncdn.wenlvti.net/app_static/minnan/images/home/Button13.png",
               "size": 50,
               "size": 50,
               "link": [
               "link": [
                 "/pages/article/data/list",
                 "/pages/article/data/list",
@@ -48,7 +48,7 @@
             },
             },
             {
             {
               "title": "文化新视角",
               "title": "文化新视角",
-              "icon": "https://mncdn.wenlvti.net/app_static/minnan/images/home/Button4.png",
+              "icon": "https://mncdn.wenlvti.net/app_static/minnan/images/home/Button14.png",
               "size": 50,
               "size": 50,
               "link": [
               "link": [
                 "/pages/article/data/list",
                 "/pages/article/data/list",
@@ -60,7 +60,7 @@
             },
             },
             {
             {
               "title": "世界走透透",
               "title": "世界走透透",
-              "icon": "https://mncdn.wenlvti.net/app_static/minnan/images/home/Button5.png",
+              "icon": "https://mncdn.wenlvti.net/app_static/minnan/images/home/Button15.png",
               "size": 50,
               "size": 50,
               "link": [
               "link": [
                 "/pages/article/data/list",
                 "/pages/article/data/list",
@@ -72,7 +72,7 @@
             },
             },
             {
             {
               "title": "来厦门䢐迌",
               "title": "来厦门䢐迌",
-              "icon": "https://mncdn.wenlvti.net/app_static/minnan/images/home/Button6.png",
+              "icon": "https://mncdn.wenlvti.net/app_static/minnan/images/home/Button16.png",
               "size": 50,
               "size": 50,
               "link": [
               "link": [
                 "/pages/article/data/list",
                 "/pages/article/data/list",
@@ -151,7 +151,7 @@
                     }
                     }
                   },
                   },
                   {
                   {
-                    "title": "闽南文化景区",
+                    "title": "景区、景点",
                     "icon": "icon-task-environment-3",
                     "icon": "icon-task-environment-3",
                     "data": {
                     "data": {
                       "type": "serializedApi",
                       "type": "serializedApi",
@@ -164,13 +164,13 @@
               "type": "MapBlock"
               "type": "MapBlock"
             },
             },
             {
             {
-              "text": "精彩推荐",
+              "text": "精彩瞬间",
               "data": {
               "data": {
                 "type": "serializedApi",
                 "type": "serializedApi",
                 "name": "ProjectsContent"
                 "name": "ProjectsContent"
               },
               },
               "showMore": false,
               "showMore": false,
-              "detailsPage": "/pages/inhert/intangible/details",
+              "detailsPage": "/pages/article/data/details?pageConfigName=intangible-details",
               "dataSolve": [
               "dataSolve": [
                 "common"
                 "common"
               ],
               ],
@@ -183,7 +183,7 @@
                 "name": "UnmoveableContent"
                 "name": "UnmoveableContent"
               },
               },
               "showTitle": false,
               "showTitle": false,
-              "detailsPage": "/pages/inhert/artifact/details",
+              "detailsPage": "/pages/article/data/details?pageConfigName=artifact-details",
               "dataSolve": [
               "dataSolve": [
                 "common"
                 "common"
               ],
               ],
@@ -220,10 +220,10 @@
                   "text": "世界闽南文化交流中心",
                   "text": "世界闽南文化交流中心",
                   "type": "",
                   "type": "",
                   "data": {
                   "data": {
-                    "type": "commonContent",
+                    "type": "detailContent",
                     "params": {
                     "params": {
-                      "modelId": 18,
-                      "mainBodyColumnId": 232
+                      "id": 8494,
+                      "modelId": 14
                     }
                     }
                   }
                   }
                 },
                 },
@@ -368,7 +368,7 @@
                     }
                     }
                   },
                   },
                   "morePage": "/pages/travel/fashion/list",
                   "morePage": "/pages/travel/fashion/list",
-                  "detailsPage": "/pages/video/details",
+                  "detailsPage": "/pages/article/data/details?pageConfigName=songs-details",
                   "type": "large-image"
                   "type": "large-image"
                 },
                 },
                 {
                 {
@@ -608,7 +608,7 @@
                     }
                     }
                   },
                   },
                   "morePage": "/pages/travel/scenic-spot/list",
                   "morePage": "/pages/travel/scenic-spot/list",
-                  "detailsPage": "/pages/inhert/intangible/details",
+                  "detailsPage": "/pages/article/data/details?pageConfigName=intangible-details",
                   "type": ""
                   "type": ""
                 },
                 },
                 {
                 {
@@ -629,6 +629,12 @@
                   "type": "horizontal-large"
                   "type": "horizontal-large"
                 },
                 },
                 {
                 {
+                  "text": "闽南节庆日历",
+                  "data": null,
+                  "morePage": "/pages/travel/calendar/index",
+                  "type": "CalendarBlock"
+                },
+                {
                   "text": "闽南美食",
                   "text": "闽南美食",
                   "data": {
                   "data": {
                     "type": "commonContent",
                     "type": "commonContent",
@@ -682,12 +688,6 @@
                   "morePage": "/pages/travel/fashion/list",
                   "morePage": "/pages/travel/fashion/list",
                   "detailsPage": "/pages/video/details",
                   "detailsPage": "/pages/video/details",
                   "type": "large-grid2"
                   "type": "large-grid2"
-                },
-                {
-                  "text": "闽南节庆日历",
-                  "data": null,
-                  "morePage": "/pages/travel/calendar/index",
-                  "type": "CalendarBlock"
                 }
                 }
               ]
               ]
             }
             }
@@ -715,7 +715,7 @@
               "dataSolve": [
               "dataSolve": [
                 "ich"
                 "ich"
               ],
               ],
-              "detailsPage": "/pages/inhert/seminar/details",
+              "detailsPage": "/pages/article/data/details?pageConfigName=seminar-details",
               "dropdownDefines": [
               "dropdownDefines": [
                 {
                 {
                   "key": "level",
                   "key": "level",
@@ -777,7 +777,7 @@
               "dataSolve": [
               "dataSolve": [
                 "ich"
                 "ich"
               ],
               ],
-              "detailsPage": "/pages/inhert/intangible/details",
+              "detailsPage": "/pages/article/data/details?pageConfigName=intangible-details",
               "dropdownDefines": [
               "dropdownDefines": [
                 {
                 {
                   "key": "ichType",
                   "key": "ichType",
@@ -828,7 +828,7 @@
               "dataSolve": [
               "dataSolve": [
                 "ich"
                 "ich"
               ],
               ],
-              "detailsPage": "/pages/inhert/seminar/details",
+              "detailsPage": "/pages/article/data/details?pageConfigName=seminar-details",
               "dropdownDefines": [
               "dropdownDefines": [
                 {
                 {
                   "key": "region",
                   "key": "region",
@@ -876,7 +876,7 @@
               "dataSolve": [
               "dataSolve": [
                 "ich"
                 "ich"
               ],
               ],
-              "detailsPage": "/pages/inhert/artifact/details",
+              "detailsPage": "/pages/article/data/details?pageConfigName=artifact-details",
               "itemType": "image-large-2",
               "itemType": "image-large-2",
               "showTotal": true,
               "showTotal": true,
               "dropdownDefines": [
               "dropdownDefines": [
@@ -984,7 +984,7 @@
                     "ich"
                     "ich"
                   ],
                   ],
                   "morePage": "/pages/article/data/list?pageConfigName=intangible&tab=0",
                   "morePage": "/pages/article/data/list?pageConfigName=intangible&tab=0",
-                  "detailsPage": "/pages/inhert/intangible/details",
+                  "detailsPage": "/pages/article/data/details?pageConfigName=intangible-details",
                   "type": "horizontal-large"
                   "type": "horizontal-large"
                 },
                 },
                 {
                 {
@@ -996,8 +996,8 @@
                   "dataSolve": [
                   "dataSolve": [
                     "ich"
                     "ich"
                   ],
                   ],
-                  "morePage": "/pages/inhert/inheritor/list",
-                  "detailsPage": "/pages/inhert/inheritor/details",
+                  "morePage": "/pages/article/data/list?pageConfigName=inheritor&tab=0",
+                  "detailsPage": "/pages/article/data/details?pageConfigName=inheritor-details",
                   "type": "horizontal-large"
                   "type": "horizontal-large"
                 },
                 },
                 {
                 {
@@ -1054,7 +1054,7 @@
                 "ich"
                 "ich"
               ],
               ],
               "morePage": "/pages/article/data/list?pageConfigName=artifact&tab=0",
               "morePage": "/pages/article/data/list?pageConfigName=artifact&tab=0",
-              "detailsPage": "/pages/inhert/artifact/details",
+              "detailsPage": "/pages/article/data/details?pageConfigName=artifact-details",
               "itemType": "image-large-2",
               "itemType": "image-large-2",
               "dropdownDefines": [
               "dropdownDefines": [
                 {
                 {
@@ -1118,7 +1118,7 @@
               "dataSolve": [
               "dataSolve": [
                 "ich"
                 "ich"
               ],
               ],
-              "detailsPage": "/pages/inhert/artifact/details",
+              "detailsPage": "/pages/article/data/details?pageConfigName=artifact-details",
               "itemType": "image-large-2",
               "itemType": "image-large-2",
               "dropdownDefines": [],
               "dropdownDefines": [],
               "preInsertCategorys": [
               "preInsertCategorys": [
@@ -1152,7 +1152,8 @@
               "dataSolve": [
               "dataSolve": [
                 "ich"
                 "ich"
               ],
               ],
-              "width": 250
+              "width": 250,
+              "visible": false
             }
             }
           ]
           ]
         }
         }
@@ -1199,7 +1200,14 @@
               "type": "list",
               "type": "list",
               "data": {
               "data": {
                 "type": "serializedApi",
                 "type": "serializedApi",
-                "name": "NewsIndexContent"
+                "name": "NewsIndexContent",
+                "params": {
+                  "mainBodyColumnId": [
+                    228,
+                    298,
+                    299
+                  ]
+                }
               },
               },
               "visible": false,
               "visible": false,
               "dataSolve": [
               "dataSolve": [
@@ -1218,8 +1226,6 @@
       "content": {
       "content": {
         "type": "Details",
         "type": "Details",
         "props": {
         "props": {
-          "commonRefName": "作品",
-          "commonRefTarget": "product",
           "introBlockDescs": [
           "introBlockDescs": [
             {
             {
               "label": "项目级别",
               "label": "项目级别",
@@ -1247,7 +1253,7 @@
             },
             },
             {
             {
               "label": "其他级别保护单位",
               "label": "其他级别保护单位",
-              "key": "otherLevel"
+              "key": "otherLevelCount"
             },
             },
             {
             {
               "label": "字号名称",
               "label": "字号名称",
@@ -1263,6 +1269,398 @@
               "type": "OtherLevelList",
               "type": "OtherLevelList",
               "props": {}
               "props": {}
             }
             }
+          ],
+          "tabs": [
+            {
+              "text": "简介",
+              "type": "intro"
+            },
+            {
+              "text": "图片",
+              "type": "images"
+            },
+            {
+              "text": "资料影像",
+              "width": 200,
+              "type": "video"
+            },
+            {
+              "text": "音频",
+              "type": "audio"
+            },
+            {
+              "text": "传习所",
+              "type": "list",
+              "define": {
+                "props": {
+                  "showTab": false,
+                  "showSearch": false,
+                  "itemType": "article-common",
+                  "detailsPage": "/pages/article/data/details?pageConfigName=seminar-details",
+                  "tabs": [
+                    {
+                      "text": "Root",
+                      "type": "list",
+                      "data": {
+                        "type": "parentKey",
+                        "key": "ichSitesList"
+                      },
+                      "dataSolve": []
+                    }
+                  ]
+                }
+              }
+            },
+            {
+              "text": "传承人",
+              "type": "list",
+              "key": "inheritorsList",
+              "define": {
+                "props": {
+                  "showTab": false,
+                  "showSearch": false,
+                  "itemType": "image-large-2",
+                  "detailsPage": "/pages/article/data/details?pageConfigName=inheritor-details",
+                  "tabs": [
+                    {
+                      "text": "Root",
+                      "type": "list",
+                      "data": {
+                        "type": "parentKey",
+                        "key": "inheritorsList"
+                      },
+                      "dataSolve": []
+                    }
+                  ]
+                }
+              }
+            },
+            {
+              "text": "传承谱系",
+              "width": 200,
+              "type": "rich",
+              "key": "pedigree"
+            },
+            {
+              "text": "非遗作品",
+              "width": 200,
+              "type": "list",
+              "key": "worksList",
+              "visible": false,
+              "define": {
+                "props": {
+                  "showTab": false,
+                  "showSearch": false,
+                  "itemType": "image-large-2",
+                  "detailsPage": "/pages/article/data/details?pageConfigName=product-details",
+                  "tabs": [
+                    {
+                      "text": "Root",
+                      "type": "list",
+                      "data": {
+                        "type": "parentKey",
+                        "key": "worksList"
+                      },
+                      "dataSolve": []
+                    }
+                  ]
+                }
+              }
+            },
+            {
+              "text": "相关资讯",
+              "type": "list",
+              "key": "associationMeList",
+              "define": {
+                "props": {
+                  "showTab": false,
+                  "showSearch": false,
+                  "itemType": "article-common",
+                  "detailsPage": "/pages/article/details",
+                  "tabs": [
+                    {
+                      "text": "Root",
+                      "type": "list",
+                      "data": {
+                        "type": "parentKey",
+                        "key": "associationMeList"
+                      },
+                      "dataSolve": []
+                    }
+                  ]
+                }
+              }
+            },
+            {
+              "text": "地理位置",
+              "type": "map"
+            }
+          ]
+        }
+      }
+    },
+    {
+      "name": "seminar-details",
+      "title": "传习所详情页",
+      "content": {
+        "type": "Details",
+        "props": {
+          "introBlockDescs": [
+            {
+              "label": "项目级别",
+              "key": "levelText"
+            },
+            {
+              "label": "项目类别",
+              "key": "ichTypeText"
+            },
+            {
+              "label": "批次时间",
+              "key": "batchText"
+            },
+            {
+              "label": "所属区域",
+              "key": "regionText"
+            },
+            {
+              "label": "保护单位",
+              "key": "unit"
+            },
+            {
+              "label": "地址",
+              "key": "address"
+            },
+            {
+              "label": "其他级别保护单位",
+              "key": "otherLevel"
+            }
+          ],
+          "introBlocks": [],
+          "tabs": [
+            {
+              "text": "简介",
+              "type": "intro"
+            },
+            {
+              "text": "图片",
+              "type": "images"
+            },
+            {
+              "text": "视频",
+              "type": "video"
+            },
+            {
+              "text": "音频",
+              "type": "audio"
+            },
+            {
+              "text": "传习所",
+              "width": 200,
+              "type": "list",
+              "key": "ichSitesList",
+              "define": {
+                "props": {
+                  "showTab": false,
+                  "showSearch": false,
+                  "itemType": "article-common",
+                  "detailsPage": "/pages/article/data/details?pageConfigName=seminar-details",
+                  "tabs": [
+                    {
+                      "text": "Root",
+                      "type": "list",
+                      "data": {
+                        "type": "parentKey",
+                        "key": "ichSitesList"
+                      },
+                      "dataSolve": []
+                    }
+                  ]
+                }
+              }
+            },
+            {
+              "text": "传承人",
+              "width": 200,
+              "type": "list",
+              "key": "inheritorsList",
+              "define": {
+                "props": {
+                  "showTab": false,
+                  "showSearch": false,
+                  "itemType": "image-large-2",
+                  "detailsPage": "/pages/article/data/details?pageConfigName=inheritor-details",
+                  "tabs": [
+                    {
+                      "text": "Root",
+                      "type": "list",
+                      "data": {
+                        "type": "parentKey",
+                        "key": "inheritorsList"
+                      },
+                      "dataSolve": []
+                    }
+                  ]
+                }
+              }
+            },
+            {
+              "text": "相关项目",
+              "width": 200,
+              "type": "list",
+              "key": "associationMeList",
+              "define": {
+                "props": {
+                  "showTab": false,
+                  "showSearch": false,
+                  "itemType": "image-large-2",
+                  "detailsPage": "/pages/article/data/details?pageConfigName=intangible-details",
+                  "tabs": [
+                    {
+                      "text": "Root",
+                      "type": "list",
+                      "data": {
+                        "type": "parentKey",
+                        "key": "associationMeList"
+                      },
+                      "dataSolve": []
+                    }
+                  ]
+                }
+              }
+            },
+            {
+              "text": "地理位置",
+              "type": "map"
+            }
+          ]
+        }
+      }
+    },
+    {
+      "name": "inheritor-details",
+      "title": "传承人详情页",
+      "content": {
+        "type": "Details",
+        "props": {
+          "introBlockDescs": [
+            {
+              "label": "年代",
+              "key": "age"
+            },
+            {
+              "label": "民族",
+              "key": "nation"
+            },
+            {
+              "label": "性别",
+              "key": "gender",
+              "map": {
+                "1": "男",
+                "2": "女"
+              }
+            },
+            {
+              "label": "出生日期",
+              "key": "dateBirth"
+            },
+            {
+              "label": "出生地区",
+              "key": "birthplace"
+            },
+            {
+              "label": "单位",
+              "key": "unit"
+            },
+            {
+              "label": "传承项目",
+              "key": "associationMeFirstTitle"
+            },
+            {
+              "label": "传承人级别",
+              "key": "levelText"
+            },
+            {
+              "label": "公布批次",
+              "key": "batchText"
+            }
+          ],
+          "introBlocks": [],
+          "tabs": [
+            {
+              "text": "简介",
+              "type": "intro"
+            },
+            {
+              "text": "图片",
+              "type": "images",
+              "prefix": "以下照片由传承人提供"
+            },
+            {
+              "text": "视频",
+              "type": "video"
+            },
+            {
+              "text": "音频",
+              "type": "audio"
+            },
+            {
+              "text": "荣誉奖项",
+              "type": "rich",
+              "key": "prize"
+            },
+            {
+              "text": "非遗项目",
+              "type": "list",
+              "key": "worksList",
+              "define": {
+                "props": {
+                  "showTab": false,
+                  "showSearch": false,
+                  "itemType": "article-common",
+                  "detailsPage": "/pages/article/data/details?pageConfigName=intangible-details",
+                  "tabs": [
+                    {
+                      "text": "Root",
+                      "type": "list",
+                      "data": {
+                        "type": "parentKey",
+                        "key": "associationMeList"
+                      },
+                      "dataSolve": []
+                    }
+                  ]
+                }
+              }
+            },
+            {
+              "text": "传习所",
+              "width": 200,
+              "type": "list",
+              "key": "ichSitesList",
+              "define": {
+                "props": {
+                  "showTab": false,
+                  "showSearch": false,
+                  "itemType": "article-common",
+                  "detailsPage": "/pages/article/data/details?pageConfigName=seminar-details",
+                  "tabs": [
+                    {
+                      "text": "Root",
+                      "type": "list",
+                      "data": {
+                        "type": "parentKey",
+                        "key": "ichSitesList"
+                      },
+                      "dataSolve": []
+                    }
+                  ]
+                }
+              }
+            },
+            {
+              "text": "地理位置",
+              "type": "map"
+            }
           ]
           ]
         }
         }
       }
       }
@@ -1287,10 +1685,13 @@
             {
             {
               "text": "视频",
               "text": "视频",
               "type": "list",
               "type": "list",
+              "key": "associationMeList",
               "define": {
               "define": {
                 "props": {
                 "props": {
                   "showTab": false,
                   "showTab": false,
                   "showSearch": false,
                   "showSearch": false,
+                  "itemType": "image-large-2",
+                  "detailsPage": "/pages/video/details",
                   "tabs": [
                   "tabs": [
                     {
                     {
                       "text": "Root",
                       "text": "Root",
@@ -1299,8 +1700,6 @@
                         "type": "parentKey",
                         "type": "parentKey",
                         "key": "associationMeList"
                         "key": "associationMeList"
                       },
                       },
-                      "itemType": "image-large-2",
-                      "detailsPage": "/pages/video/details",
                       "dataSolve": []
                       "dataSolve": []
                     }
                     }
                   ]
                   ]
@@ -1310,6 +1709,83 @@
           ]
           ]
         }
         }
       }
       }
+    },
+    {
+      "name": "artifact-details",
+      "title": "文物详情",
+      "content": {
+        "type": "Details",
+        "props": {
+          "introBlockDescs": [
+            {
+              "label": "开放时间",
+              "key": "openStatusText"
+            },
+            {
+              "label": "年代",
+              "key": "age"
+            },
+            {
+              "label": "级别",
+              "key": "levelText"
+            },
+            {
+              "label": "所属区域",
+              "key": "regionText"
+            },
+            {
+              "label": "文物类型",
+              "key": "crTypeText"
+            }
+          ],
+          "introBlocks": [
+            {
+              "type": "NavTo"
+            }
+          ],
+          "tabs": [
+            {
+              "text": "简介",
+              "type": "intro"
+            },
+            {
+              "text": "图片",
+              "type": "images"
+            },
+            {
+              "text": "视频",
+              "type": "video"
+            },
+            {
+              "text": "音频",
+              "type": "audio"
+            },
+            {
+              "text": "保护范围",
+              "width": 200,
+              "type": "rich",
+              "key": "protectedArea"
+            },
+            {
+              "text": "建筑环境",
+              "width": 200,
+              "type": "rich",
+              "key": "environment"
+            },
+            {
+              "text": "价值评估",
+              "width": 200,
+              "type": "rich",
+              "key": "value"
+            },
+            {
+              "text": "地理位置",
+              "width": 200,
+              "type": "map"
+            }
+          ]
+        }
+      }
     }
     }
   ]
   ]
 }
 }

+ 4 - 0
src/pages/article/data/defines/Details.ts

@@ -69,6 +69,10 @@ export interface IHomeCommonCategoryDetailTabItemInternalDefine {
    * * 'audio' - 单音频
    * * 'audio' - 单音频
    */
    */
   type: 'intro'|'images'|'video'|'audio',
   type: 'intro'|'images'|'video'|'audio',
+  /**
+   * 前缀文字
+   */
+  prefix?: string,
 }
 }
 /**
 /**
  * TAB定义 - 类型:嵌套子分类 - 列表类型
  * TAB定义 - 类型:嵌套子分类 - 列表类型

+ 2 - 0
src/pages/article/data/defines/List.ts

@@ -202,6 +202,8 @@ export function CommonCategoryListTabNestCategoryDataToContent(
   switch (data.type) {
   switch (data.type) {
     case 'serializedApi':
     case 'serializedApi':
       return CommonCategoryDynamicDataSerializedApi(data);
       return CommonCategoryDynamicDataSerializedApi(data);
+    case 'detailContent':
+      return data;
     case 'parentKey':
     case 'parentKey':
       throw new Error(`未实现的动态数据接口 ${data.type}`);
       throw new Error(`未实现的动态数据接口 ${data.type}`);
     case 'request':
     case 'request':

+ 42 - 10
src/pages/article/data/editor/MiniProgramEditor.vue

@@ -99,7 +99,7 @@
             />
             />
           </a-form-item>
           </a-form-item>
           <a-form-item v-if="addPageTemplate" label="模板">
           <a-form-item v-if="addPageTemplate" label="模板">
-            <span>{{ addPageTemplate === 'Home' ? 'Home' : 'CommonList' }}</span>
+            <span>{{ addPageTemplate }}</span>
           </a-form-item>
           </a-form-item>
         </a-form>
         </a-form>
       </a-modal>
       </a-modal>
@@ -115,6 +115,7 @@
                 <a-menu @click="onAddPageMenuClick">
                 <a-menu @click="onAddPageMenuClick">
                   <a-menu-item key="Home">Home</a-menu-item>
                   <a-menu-item key="Home">Home</a-menu-item>
                   <a-menu-item key="CommonList">CommonList</a-menu-item>
                   <a-menu-item key="CommonList">CommonList</a-menu-item>
+                  <a-menu-item key="Details">Details</a-menu-item>
                 </a-menu>
                 </a-menu>
               </template>
               </template>
             </a-dropdown>
             </a-dropdown>
@@ -158,6 +159,7 @@
           <PropsEditorTree
           <PropsEditorTree
             v-else
             v-else
             :page="selectedPage"
             :page="selectedPage"
+            v-model:testDetailId="testDetailId"
           />
           />
         </div>
         </div>
 
 
@@ -169,6 +171,7 @@
               ref="previewRef"
               ref="previewRef"
               :editor-json="currentEditorJson"
               :editor-json="currentEditorJson"
               :selected-page="selectedPage"
               :selected-page="selectedPage"
+              :test-detail-id="testDetailId"
             />
             />
           </div>
           </div>
         </div>
         </div>
@@ -182,7 +185,7 @@ import { computed, onMounted, provide, ref } from 'vue';
 import { ObjectUtils } from '@imengyu/imengyu-utils';
 import { ObjectUtils } from '@imengyu/imengyu-utils';
 import { DownOutlined, DownloadOutlined, InfoCircleFilled, PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
 import { DownOutlined, DownloadOutlined, InfoCircleFilled, PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
 import { message, Modal } from 'ant-design-vue';
 import { message, Modal } from 'ant-design-vue';
-import type { IHomeCommonCategoryDefine, IHomeCommonCategoryListDefine, IHomeCommonCategoryHomeDefine } from '../CommonCategoryDefine';
+import type { IHomeCommonCategoryDefine, IHomeCommonCategoryDetailDefine, IHomeCommonCategoryListDefine, IHomeCommonCategoryHomeDefine } from '../CommonCategoryDefine';
 import DefaultEditorJson from '../DefaultCategory.json';
 import DefaultEditorJson from '../DefaultCategory.json';
 import PropsEditorTree from './subpart/PropsEditorTree.vue';
 import PropsEditorTree from './subpart/PropsEditorTree.vue';
 import EditorPreview from './subpart/EditorPreview.vue';
 import EditorPreview from './subpart/EditorPreview.vue';
@@ -210,12 +213,14 @@ const currentShowConfigName = computed(() => {
     return historyList.value.find(item => item.id === currentHistoryId.value)?.name ?? '未知';
     return historyList.value.find(item => item.id === currentHistoryId.value)?.name ?? '未知';
 });
 });
 
 
+const testDetailId = ref<number>(0);
+
 const saveAsModalVisible = ref(false);
 const saveAsModalVisible = ref(false);
 const saveAsVersionName = ref('');
 const saveAsVersionName = ref('');
 const saveAsLoading = ref(false);
 const saveAsLoading = ref(false);
 
 
 const addPageModalVisible = ref(false);
 const addPageModalVisible = ref(false);
-const addPageTemplate = ref<'Home' | 'CommonList' | null>(null);
+const addPageTemplate = ref<'Home' | 'CommonList' | 'Details' | null>(null);
 const addPageKey = ref('');
 const addPageKey = ref('');
 const addPageTitle = ref('');
 const addPageTitle = ref('');
 const addPageLoading = ref(false);
 const addPageLoading = ref(false);
@@ -262,12 +267,27 @@ function createCommonListPageTemplate(): IHomeCommonCategoryListDefine {
   };
   };
 }
 }
 
 
+/** Details 模板 */
+function createDetailPageTemplate(): IHomeCommonCategoryDetailDefine {
+  return {
+    type: 'Details',
+    props: {
+      showHead: true,
+      hasInternalTabs: false,
+      introBlockDescs: [],
+      introBlocks: [],
+      tabs: [],
+    },
+  };
+}
+
 function onAddPageMenuClick(e: { key: string }) {
 function onAddPageMenuClick(e: { key: string }) {
-  const template = e.key as 'Home' | 'CommonList';
+  const template = e.key as 'Home' | 'CommonList' | 'Details';
   addPageTemplate.value = template;
   addPageTemplate.value = template;
-  const prefix = template === 'Home' ? 'home' : 'list';
-  addPageKey.value = getUniquePageName(prefix);
-  addPageTitle.value = template === 'Home' ? '首页' : '列表页';
+  const prefixMap = { Home: 'home', CommonList: 'list', Details: 'detail' } as const;
+  const titleMap = { Home: '首页', CommonList: '列表页', Details: '详情页' } as const;
+  addPageKey.value = getUniquePageName(prefixMap[template] ?? 'page');
+  addPageTitle.value = titleMap[template] ?? '页面';
   addPageModalVisible.value = true;
   addPageModalVisible.value = true;
 }
 }
 
 
@@ -291,9 +311,21 @@ function confirmAddPage(): Promise<void> | void {
   const template = addPageTemplate.value;
   const template = addPageTemplate.value;
   if (!template) return Promise.reject();
   if (!template) return Promise.reject();
   addPageLoading.value = true;
   addPageLoading.value = true;
-  const content = template === 'Home'
-    ? createHomePageTemplate()
-    : createCommonListPageTemplate();
+  let content: IHomeCommonCategoryHomeDefine | IHomeCommonCategoryListDefine | IHomeCommonCategoryDetailDefine;
+  switch (template) {
+    case 'Home':
+      content = createHomePageTemplate();
+      break;
+    case 'CommonList':
+      content = createCommonListPageTemplate();
+      break;
+    case 'Details':
+      content = createDetailPageTemplate();
+      break;
+    default:
+      message.warning(`不支持的模板:${template}`);
+      return Promise.reject();
+  }
   currentEditorJson.value = {
   currentEditorJson.value = {
     ...currentEditorJson.value,
     ...currentEditorJson.value,
     page: [...pages, { name: key, title, content }],
     page: [...pages, { name: key, title, content }],

+ 33 - 0
src/pages/article/data/editor/components/DynamicDataEditor.vue

@@ -30,6 +30,25 @@
         </a-form-item>
         </a-form-item>
       </template>
       </template>
 
 
+      <template v-else-if="currentType === 'detailContent'">
+        
+        <a-form-item label="内容 ID">
+          <a-input
+            :value="(modelValue as IHomeCommonCategoryDynamicDataDetailContent)?.params?.id"
+            @change="(e: Event) => setDetailContent('id', Number((e.target as HTMLInputElement)?.value))"
+          />
+        </a-form-item>
+        <a-form-item label="模型 ID (modelId)">
+          <a-input-number
+            :value="(modelValue as IHomeCommonCategoryDynamicDataDetailContent)?.params?.modelId"
+            style="width: 100%"
+            :min="1"
+            placeholder="可选,模型ID"
+            @update:value="(v: number | undefined) => setDetailContent('modelId', v)"
+          />
+        </a-form-item>
+      </template>
+
       <template v-else-if="currentType === 'serializedApi'">
       <template v-else-if="currentType === 'serializedApi'">
         <a-form-item label="序列化接口名称 (name)">
         <a-form-item label="序列化接口名称 (name)">
           <a-select
           <a-select
@@ -95,6 +114,7 @@ import type {
   IHomeCommonCategoryDynamicDataCommonContent,
   IHomeCommonCategoryDynamicDataCommonContent,
   IHomeCommonCategoryDynamicDataSerializedApi,
   IHomeCommonCategoryDynamicDataSerializedApi,
   IHomeCommonCategoryDynamicDataRequest,
   IHomeCommonCategoryDynamicDataRequest,
+  IHomeCommonCategoryDynamicDataDetailContent,
 } from '@/pages/article/data/CommonCategoryDynamicData';
 } from '@/pages/article/data/CommonCategoryDynamicData';
 import { SerializedApiMap } from '@/pages/article/data/CommonCategoryDynamicData';
 import { SerializedApiMap } from '@/pages/article/data/CommonCategoryDynamicData';
 
 
@@ -108,6 +128,7 @@ const emit = defineEmits<{
 
 
 const typeOptions = [
 const typeOptions = [
   { value: 'commonContent', label: '通用内容 (commonContent)' },
   { value: 'commonContent', label: '通用内容 (commonContent)' },
+  { value: 'detailContent', label: '详情内容 (detailContent)' },
   { value: 'serializedApi', label: '序列化接口 (serializedApi)' },
   { value: 'serializedApi', label: '序列化接口 (serializedApi)' },
   { value: 'request', label: '请求 (request)' },
   { value: 'request', label: '请求 (request)' },
 ];
 ];
@@ -170,6 +191,11 @@ function onTypeChange(type: 'commonContent' | 'serializedApi' | 'request') {
       method: 'GET',
       method: 'GET',
       url: '',
       url: '',
     });
     });
+  } else if (type === 'detailContent') {
+    emit('update:modelValue', {
+      type: 'detailContent',
+      params: { id: 0, modelId: 0 },
+    });
   }
   }
 }
 }
 
 
@@ -213,6 +239,13 @@ function setRequest(key: 'method' | 'url', value: string) {
   emit('update:modelValue', { ...cur, [key]: value });
   emit('update:modelValue', { ...cur, [key]: value });
 }
 }
 
 
+function setDetailContent(key: 'id' | 'modelId', value: number | undefined) {
+  const cur = props.modelValue as IHomeCommonCategoryDynamicDataDetailContent | undefined;
+  if (!cur || cur.type !== 'detailContent') return;
+  const next = { ...cur, params: { ...cur.params, [key]: value } };
+  emit('update:modelValue', next);
+}
+
 const requestQuerysJson = computed(() => {
 const requestQuerysJson = computed(() => {
   const cur = props.modelValue as IHomeCommonCategoryDynamicDataRequest | undefined;
   const cur = props.modelValue as IHomeCommonCategoryDynamicDataRequest | undefined;
   if (!cur?.querys) return '';
   if (!cur?.querys) return '';

+ 48 - 17
src/pages/article/data/editor/components/LinkPathEditor.vue

@@ -76,17 +76,54 @@ import type { IHomeCommonCategoryDefine } from '@/pages/article/data/CommonCateg
 import PagesJson from '@/pages.json';
 import PagesJson from '@/pages.json';
 import { CommonCategoryListPath } from '@/pages/article/data/CommonCategoryPathDefine';
 import { CommonCategoryListPath } from '@/pages/article/data/CommonCategoryPathDefine';
 
 
+/** 从 URL 字符串中解析路径和 ? 后面的查询参数 */
+function parseUrlParams(url: string): { path: string; params: Record<string, string> } {
+  if (!url || typeof url !== 'string') return { path: '', params: {} };
+  const i = url.indexOf('?');
+  if (i === -1) return { path: url.trim(), params: {} };
+  const path = url.slice(0, i).trim();
+  const search = url.slice(i + 1);
+  const params: Record<string, string> = {};
+  for (const pair of search.split('&')) {
+    const eq = pair.indexOf('=');
+    const key = eq === -1 ? pair : pair.slice(0, eq);
+    const value = eq === -1 ? '' : pair.slice(eq + 1);
+    if (key) {
+      try {
+        params[decodeURIComponent(key.trim())] = decodeURIComponent(value);
+      } catch {
+        params[key.trim()] = value;
+      }
+    }
+  }
+  return { path, params };
+}
+
+/** 根据路径和参数对象拼接成 URL 字符串(? 后面部分) */
+function buildUrlWithParams(path: string, params: Record<string, string>): string {
+  const pathTrim = (path || '').trim();
+  const keys = Object.keys(params).filter(
+    (k) => k != null && String(params[k]) !== ''
+  );
+  if (keys.length === 0) return pathTrim;
+  const search = keys
+    .map((k) => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
+    .join('&');
+  return pathTrim + '?' + search;
+}
+
 // 定义props
 // 定义props
 const props = defineProps<{
 const props = defineProps<{
-  modelValue?: string|[string, object]|undefined;
+  /** 跳转路径,可为带查询参数的完整 URL 字符串(? 后部分会被解析为参数) */
+  modelValue?: string | undefined;
   /**
   /**
    * 是否禁用参数设置
    * 是否禁用参数设置
    * @default false
    * @default false
    */
    */
-  noParams?: boolean|undefined;
+  noParams?: boolean | undefined;
 }>();
 }>();
 const emit = defineEmits<{
 const emit = defineEmits<{
-  (e: 'update:modelValue', value: string|[string, object]): void;
+  (e: 'update:modelValue', value: string): void;
 }>();
 }>();
 
 
 const pageList = inject<(IHomeCommonCategoryDefine['page'][0])[]>('pageList', []);
 const pageList = inject<(IHomeCommonCategoryDefine['page'][0])[]>('pageList', []);
@@ -111,19 +148,16 @@ const internalPages = computed(() => {
 
 
 let noUpdate = false;
 let noUpdate = false;
 
 
-// 监听参数变化
+// 监听 modelValue:从 URL 字符串解析路径与 ? 后参数
 watch(
 watch(
   () => props.modelValue,
   () => props.modelValue,
   (newValue) => {
   (newValue) => {
     noUpdate = true;
     noUpdate = true;
-    if (Array.isArray(newValue)) {
-      linkPath.value = newValue[0] || '';
-      localParams.value = newValue[1] as Record<string, string> || {};
-    } else {
-      linkPath.value = newValue || '';
-      localParams.value = {};
-    }
-    if (linkPath.value == CommonCategoryListPath) {
+    const urlStr = typeof newValue === 'string' ? newValue : '';
+    const { path, params } = parseUrlParams(urlStr);
+    linkPath.value = path;
+    localParams.value = params;
+    if (linkPath.value === CommonCategoryListPath) {
       pathType.value = 'dynamic-list';
       pathType.value = 'dynamic-list';
       localPageConfigName.value = localParams.value.pageConfigName || '';
       localPageConfigName.value = localParams.value.pageConfigName || '';
     } else if (linkPath.value.startsWith('/pages/')) {
     } else if (linkPath.value.startsWith('/pages/')) {
@@ -145,7 +179,7 @@ watch(
   { deep: true }
   { deep: true }
 );
 );
 
 
-// 更新链接
+// 更新链接:输出为带 ? 参数的完整 URL 字符串
 const updateLink = () => {
 const updateLink = () => {
   if (noUpdate) {
   if (noUpdate) {
     return;
     return;
@@ -160,10 +194,7 @@ const updateLink = () => {
     };
     };
     linkPath.value = CommonCategoryListPath;
     linkPath.value = CommonCategoryListPath;
   }
   }
-  emit('update:modelValue', [
-    linkPath.value,
-    localParams.value,
-  ]);
+  emit('update:modelValue', buildUrlWithParams(linkPath.value, localParams.value));
 };
 };
 </script>
 </script>
 
 

+ 238 - 0
src/pages/article/data/editor/editors/DetailPropsEditor.vue

@@ -0,0 +1,238 @@
+<template>
+  <div class="detail-props-editor">
+    <a-form :labelCol="{ span: 6 }" size="small">
+      <a-form-item label="显示头部">
+        <a-checkbox v-model:checked="props.props.showHead" :indeterminate="props.props.showHead === undefined">
+          默认:显示
+        </a-checkbox>
+      </a-form-item>
+      <a-form-item label="测试内容ID">
+        <a-input-number 
+          :value="props.testDetailId" 
+          @update:value="emit('update:testDetailId', $event)"
+          style="width: 100%"
+        />
+      </a-form-item>
+    </a-form>
+
+    <a-collapse v-model:activeKey="activeKeys" class="props-collapse">
+      <a-collapse-panel key="introBlockDescs" header="简介块描述项 (introBlockDescs)">
+        <div v-for="(item, i) in introBlockDescsList" :key="`desc-${i}`" class="nested-item">
+          <a-form :labelCol="{ span: 6 }" size="small">
+            <a-form-item label="label">
+              <a-input v-model:value="item.label" placeholder="显示标签" />
+            </a-form-item>
+            <a-form-item label="key">
+              <a-input v-model:value="item.key" placeholder="数据键名" />
+            </a-form-item>
+            <a-form-item label="映射关系">
+              <KeyValueEditor v-model:value="item.map" />
+            </a-form-item>
+            <a-popconfirm title="确定删除?" @confirm="removeIntroBlockDesc(i)">
+              <a-button type="link" danger size="small">删除</a-button>
+            </a-popconfirm>
+          </a-form>
+        </div>
+        <a-button type="dashed" block size="small" @click="addIntroBlockDesc">+ 添加描述项</a-button>
+      </a-collapse-panel>
+
+      <a-collapse-panel key="introBlocks" header="简介下方块 (introBlocks)">
+        <div v-for="(item, i) in introBlocksList" :key="`block-${i}`" class="nested-item">
+          <a-form :labelCol="{ span: 6 }" size="small">
+            <a-form-item label="类型">
+              <a-input v-model:value="item.type" placeholder="块类型" />
+            </a-form-item>
+            <a-form-item label="属性">
+              <KeyValueEditor v-model:value="item.props" />
+            </a-form-item>
+            <a-popconfirm title="确定删除?" @confirm="removeIntroBlock(i)">
+              <a-button type="link" danger size="small">删除</a-button>
+            </a-popconfirm>
+          </a-form>
+        </div>
+        <a-button type="dashed" block size="small" @click="addIntroBlock">+ 添加块</a-button>
+      </a-collapse-panel>
+
+      <a-collapse-panel key="tabs" header="详情 Tab (tabs)">
+        <div v-for="(tab, i) in tabItems" :key="tabKey(tab, i)" class="nested-item tab-item">
+          <a-collapse>
+            <a-collapse-panel :key="i" :header="tabHeader(tab)">
+              <a-form :labelCol="{ span: 4 }" size="small">
+                <a-form-item label="文本">
+                  <a-input v-model:value="tab.text" />
+                </a-form-item>
+                <a-form-item label="类型">
+                  <a-select
+                    v-model:value="tab.type"
+                    style="width: 100%"
+                    :options="detailTabTypeOptions"
+                    @change="(v: string) => onTabTypeChange(tab, v)"
+                  />
+                </a-form-item>
+                <a-form-item v-if="tab.type === 'images'" label="前缀文字">
+                  <a-input v-model:value="tab.prefix" />
+                </a-form-item>
+                <a-form-item label="数据键 key">
+                  <a-input v-model:value="tab.key" placeholder="对应内容数据键" />
+                </a-form-item>
+                <a-form-item label="TAB 宽度">
+                  <a-input-number v-model:value="tab.width" style="width: 100%" />
+                </a-form-item>
+                <a-form-item label="可见">
+                  <a-checkbox v-model:checked="tab.visible" :indeterminate="tab.visible === undefined" />
+                </a-form-item>
+
+                <template v-if="tab.type === 'list'">
+                  <a-divider>列表配置</a-divider>
+                  <CommonListPropsEditor :props="ensureListDefine(tab).define.props" />
+                </template>
+                <template v-else-if="tab.type === 'nestCategory'">
+                  <a-form-item label="子栏目">
+                    <NestCategoryEditor v-model:categorys="(tab as IHomeCommonCategoryDetailTabItemNestCategoryDefine).categorys" />
+                  </a-form-item>
+                </template>
+                <template v-else-if="tab.type === 'rich'">
+                  <a-form-item label="富文本数据键">
+                    <a-input v-model:value="tab.key" placeholder="与上方数据键一致" />
+                  </a-form-item>
+                </template>
+
+                <a-popconfirm title="确认删除该 Tab?" @confirm="removeTab(i)">
+                  <a-button type="link" danger size="small">删除 Tab</a-button>
+                </a-popconfirm>
+              </a-form>
+            </a-collapse-panel>
+          </a-collapse>
+        </div>
+        <a-button type="dashed" block size="small" @click="addTab">+ 添加 Tab</a-button>
+      </a-collapse-panel>
+    </a-collapse>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue';
+import type {
+  IHomeCommonCategoryDetailDefine,
+  IHomeCommonCategoryDetailTabItemDefine,
+  IHomeCommonCategoryDetailTabItemListDefine,
+  IHomeCommonCategoryDetailTabItemNestCategoryDefine,
+} from '../../defines/Details';
+import type { IHomeCommonCategoryListDefine } from '../../CommonCategoryDefine';
+import CommonListPropsEditor from './CommonListPropsEditor.vue';
+import NestCategoryEditor from '../subpart/NestCategoryEditor.vue';
+import KeyValueEditor from '../components/KeyValueEditor.vue';
+
+const props = defineProps<{
+  props: IHomeCommonCategoryDetailDefine['props'];
+  testDetailId: number;
+}>();
+const emit = defineEmits<{
+  (e: 'update:testDetailId', value: number): void;
+}>();
+
+const activeKeys = ref<string[]>(['introBlockDescs', 'introBlocks', 'tabs']);
+
+const detailTabTypeOptions = [
+  { value: 'intro', label: '简介' },
+  { value: 'images', label: '相册图片' },
+  { value: 'video', label: '视频' },
+  { value: 'audio', label: '音频' },
+  { value: 'list', label: '列表' },
+  { value: 'rich', label: '富文本' },
+  { value: 'map', label: '地图' },
+  { value: 'nestCategory', label: '嵌套子分类' },
+];
+
+const introBlockDescsList = computed(() => props.props?.introBlockDescs ?? []);
+const introBlocksList = computed(() => props.props?.introBlocks ?? []);
+const tabItems = computed(() => (props.props?.tabs || []) as IHomeCommonCategoryDetailTabItemDefine[]);
+
+function tabKey(tab: IHomeCommonCategoryDetailTabItemDefine, i: number) {
+  return `detail-tab-${i}-${tab.text}-${tab.type}`;
+}
+function tabHeader(tab: IHomeCommonCategoryDetailTabItemDefine) {
+  return `${tab.text || 'Tab'} (${tab.type || '?'})`;
+}
+
+function addIntroBlockDesc() {
+  props.props.introBlockDescs = props.props.introBlockDescs ?? [];
+  props.props.introBlockDescs.push({ label: '', key: '' });
+}
+function removeIntroBlockDesc(i: number) {
+  if (!props.props.introBlockDescs) return;
+  props.props.introBlockDescs.splice(i, 1);
+}
+function addIntroBlock() {
+  props.props.introBlocks = props.props.introBlocks ?? [];
+  props.props.introBlocks.push({ type: '', props: {} });
+}
+function removeIntroBlock(i: number) {
+  if (!props.props.introBlocks) return;
+  props.props.introBlocks.splice(i, 1);
+}
+
+/** 创建默认列表定义 */
+function createDefaultListDefine(): IHomeCommonCategoryListDefine {
+  return {
+    type: 'CommonList',
+    props: {
+      showTab: true,
+      showSearch: true,
+      showTotal: true,
+      tabs: [],
+    },
+  };
+}
+
+/** 确保 list 类型 tab 有 define,并返回 define */
+function ensureListDefine(tab: IHomeCommonCategoryDetailTabItemDefine): IHomeCommonCategoryDetailTabItemListDefine {
+  const t = tab as IHomeCommonCategoryDetailTabItemListDefine;
+  if (!t.define) {
+    t.define = createDefaultListDefine();
+  }
+  return t;
+}
+
+function onTabTypeChange(tab: IHomeCommonCategoryDetailTabItemDefine, newType: string) {
+  if (newType === 'list') {
+    (tab as IHomeCommonCategoryDetailTabItemListDefine).define = createDefaultListDefine();
+  } else if (newType === 'nestCategory') {
+    (tab as IHomeCommonCategoryDetailTabItemNestCategoryDefine).categorys =
+      (tab as IHomeCommonCategoryDetailTabItemNestCategoryDefine).categorys ?? [];
+  }
+}
+
+function addTab() {
+  props.props.tabs = props.props.tabs ?? [];
+  const newTab: IHomeCommonCategoryDetailTabItemDefine = {
+    text: '新 Tab',
+    key: '',
+    type: 'intro',
+    visible: true,
+  };
+  props.props.tabs.push(newTab);
+}
+function removeTab(i: number) {
+  props.props.tabs?.splice(i, 1);
+}
+</script>
+
+<style scoped>
+.detail-props-editor {
+  font-size: 12px;
+}
+.props-collapse {
+  margin-top: 8px;
+}
+.nested-item {
+  margin-bottom: 12px;
+  padding: 8px;
+  background: #fafafa;
+  border-radius: 4px;
+}
+.tab-item :deep(.ant-collapse) {
+  border: none;
+  background: transparent;
+}
+</style>

+ 12 - 0
src/pages/article/data/editor/subpart/EditorPreview.vue

@@ -16,11 +16,13 @@ import { COMMON_CATEGORY_KEY } from '../../CommonCategoryGlobalLoader';
 import type { IHomeCommonCategoryDefine } from '../../CommonCategoryDefine';
 import type { IHomeCommonCategoryDefine } from '../../CommonCategoryDefine';
 import HomePage from '@/pages/home/index.vue';
 import HomePage from '@/pages/home/index.vue';
 import CommonCategoryList from '@/pages/article/data/CommonCategoryList.vue';
 import CommonCategoryList from '@/pages/article/data/CommonCategoryList.vue';
+import CommonCategoryDetail from '@/pages/article/data/CommonCategoryDetail.vue';
 import { ObjectUtils, waitTimeOut } from '@imengyu/imengyu-utils';
 import { ObjectUtils, waitTimeOut } from '@imengyu/imengyu-utils';
 
 
 const props = defineProps<{
 const props = defineProps<{
   editorJson: IHomeCommonCategoryDefine;
   editorJson: IHomeCommonCategoryDefine;
   selectedPage: IHomeCommonCategoryDefine['page'][0] | null;
   selectedPage: IHomeCommonCategoryDefine['page'][0] | null;
+  testDetailId: number;
 }>();
 }>();
 
 
 // 注入当前编辑的 JSON,供预览中的 home/list 通过 injectCommonCategory() 读取
 // 注入当前编辑的 JSON,供预览中的 home/list 通过 injectCommonCategory() 读取
@@ -43,6 +45,8 @@ const previewComponent = computed<Component | null>(() => {
       return markRaw(HomePage);
       return markRaw(HomePage);
     case 'CommonList':
     case 'CommonList':
       return markRaw(CommonCategoryList);
       return markRaw(CommonCategoryList);
+    case 'Details':
+      return markRaw(CommonCategoryDetail);
     default:
     default:
       console.error('未知页面类型:', type);
       console.error('未知页面类型:', type);
       return null;
       return null;
@@ -59,6 +63,14 @@ const previewProps = computed(() => {
       pageQuerys: {},
       pageQuerys: {},
     };
     };
   }
   }
+  if (page.content.type === 'Details') {
+    return {
+      pageConfigName: page.name,
+      pageQuerys: {
+        id: props.testDetailId,
+      },
+    };
+  }
   return {};
   return {};
 });
 });
 
 

+ 10 - 0
src/pages/article/data/editor/subpart/PropsEditorTree.vue

@@ -18,6 +18,12 @@
         v-else-if="contentType === 'CommonList'"
         v-else-if="contentType === 'CommonList'"
         :props="(props.page?.content?.props as IHomeCommonCategoryListDefine['props'])"
         :props="(props.page?.content?.props as IHomeCommonCategoryListDefine['props'])"
       />
       />
+      <DetailPropsEditor
+        v-else-if="contentType === 'Details'"
+        :props="(props.page?.content?.props as IHomeCommonCategoryDetailDefine['props'])"
+        :test-detail-id="testDetailId"
+        @update:testDetailId="emit('update:testDetailId', $event)"
+      />
       <div v-else class="unknown-type">未知模板类型: {{ contentType }}</div>
       <div v-else class="unknown-type">未知模板类型: {{ contentType }}</div>
     </div>
     </div>
   </div>
   </div>
@@ -27,17 +33,21 @@
 import { computed } from 'vue';
 import { computed } from 'vue';
 import type {
 import type {
   IHomeCommonCategoryDefine,
   IHomeCommonCategoryDefine,
+  IHomeCommonCategoryDetailDefine,
   IHomeCommonCategoryHomeDefine,
   IHomeCommonCategoryHomeDefine,
   IHomeCommonCategoryListDefine,
   IHomeCommonCategoryListDefine,
 } from '../../CommonCategoryDefine';
 } from '../../CommonCategoryDefine';
 import HomePropsEditor from '../editors/HomePropsEditor.vue';
 import HomePropsEditor from '../editors/HomePropsEditor.vue';
 import CommonListPropsEditor from '../editors/CommonListPropsEditor.vue';
 import CommonListPropsEditor from '../editors/CommonListPropsEditor.vue';
+import DetailPropsEditor from '../editors/DetailPropsEditor.vue';
 
 
 const props = defineProps<{
 const props = defineProps<{
   page: IHomeCommonCategoryDefine['page'][0];
   page: IHomeCommonCategoryDefine['page'][0];
+  testDetailId: number;
 }>();
 }>();
 const emit = defineEmits<{
 const emit = defineEmits<{
   (e: 'update:page', page: IHomeCommonCategoryDefine['page'][0]): void;
   (e: 'update:page', page: IHomeCommonCategoryDefine['page'][0]): void;
+  (e: 'update:testDetailId', value: number): void;
 }>();
 }>();
 
 
 const contentType = computed(() => props.page?.content?.type);
 const contentType = computed(() => props.page?.content?.type);

+ 1 - 5
src/pages/blocks/StatsBlock.vue

@@ -89,11 +89,7 @@ const statsLoader = useSimpleDataLoader(async () => {
         onClick: () => {
         onClick: () => {
           switch (item.title) {
           switch (item.title) {
             case '世界文化遗产':
             case '世界文化遗产':
-              navCommonList({
-                title: '世界文化遗产',
-                modelId: 17,
-                mainBodyColumnId: 310
-              });
+              navTo('/pages/article/data/details', { pageConfigName: 'artifact-details', modelId: 7518 });
               break;
               break;
             case '传统村落':
             case '传统村落':
               navTo('/pages/inhert/village/list');
               navTo('/pages/inhert/village/list');

+ 1 - 1
src/pages/home/index.vue

@@ -183,7 +183,7 @@ onShareAppMessage(() => {
     display: flex;
     display: flex;
     flex-direction: row;
     flex-direction: row;
     align-items: center;
     align-items: center;
-    justify-content: space-between;
+    justify-content: space-around;
     overflow: hidden;
     overflow: hidden;
     background: linear-gradient(180deg, #E5CDAB 0%, #F0E3D6 100%), #F7F3E8;
     background: linear-gradient(180deg, #E5CDAB 0%, #F0E3D6 100%), #F7F3E8;
     padding: 50rpx 20rpx;
     padding: 50rpx 20rpx;

+ 0 - 192
src/pages/inhert/inheritor/details.vue

@@ -1,192 +0,0 @@
-<template>
-  <DetailTabPage
-    ref="pageRef"
-    :load="load"
-    :extraTabs="[
-      {
-        id: TAB_ID_PRIZE,
-        text: '荣誉奖项',
-        width: 180,
-        visible: true,
-      },
-      {
-        id: TAB_ID_ASSOCIATION_ME,
-        text: '非遗项目',
-        width: 180,
-        visible: true,
-      },
-      {
-        id: TAB_ID_ICH_SITES,
-        text: '传习所',
-        visible: true,
-      }
-    ]"
-    :showHead="false"
-  >
-    <template #imagesPrefix>
-      <view class="d-flex flex-row justify-center align-center mt-2 mb-2">
-        <text class="size-s font-bold color-text-content">以下照片由传承人提供</text>
-      </view>
-    </template>
-    <template #extraTabs="{ content, tabCurrentId }">
-      <template v-if="tabCurrentId==TAB_ID_PRIZE">
-        <!-- 荣誉奖项 -->
-        <Parse :content="(content.prize as string)" />
-      </template>
-      <template v-else-if="tabCurrentId==TAB_ID_ASSOCIATION_ME">
-        <!-- 非遗项目 -->
-        <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 v-else-if="tabCurrentId==TAB_ID_ICH_SITES">
-        <!-- 传习所 -->
-        <CommonListPage 
-          :showSearch="false"
-          :hasBg="false"
-          :load="(page: number, pageSize: number) => loadSubList(page, pageSize, content, 'ichSitesList')"
-          detailsPage="/pages/inhert/intangible/details"
-          :detailsParams="{
-            mainBodyColumnId: SeminarContent.mainBodyColumnId,
-            modelId: SeminarContent.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 align-start">
-            <view>
-              <text :class="'d-inline size-lll font-bold color-text-content'+(content.titleBox?' border-all-text':'')">{{ content.title }}</text>
-            </view>
-            <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>
-          <Image
-            width="150"
-            height="150"
-            radius="15"
-            innerClass="flex-shrink-0" 
-            :src="content.image" mode="aspectFill"
-          />
-        </view>
-      </view> 
-    </template>
-    <template #titleExtra="{ content }">
-      <IntroBlock
-        small
-        :descItems="[
-          {
-            label: '年代',
-            value: content.age,
-          },
-          {
-            label: '民族',
-            value: content.nation,
-          },
-          {
-            label: '性别',
-            value: content.gender == '1'? '男' : '女', 
-          },
-          {
-            label: '出生日期',
-            value: content.dateBirth,
-          }, 
-          {
-            label: '出生地区',
-            value: content.birthplace,
-          },
-          {
-            label: '单位',
-            value: content.unit,
-          },
-          {
-            label: '传承项目',
-            value: content.associationMeList[0]?.title,
-          }, 
-          {
-            label: '传承人级别',
-            value: content.levelText,
-          }, 
-          {
-            label: '公布批次',
-            value: content.batchText,
-          },
-        ]"
-      />
-    </template>
-  </DetailTabPage>
-</template>
-<script setup lang="ts">
-import { useTabId, type TabControlItem } from "@/common/composeabe/TabControl";
-import { ref, type Ref } from "vue";
-import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app';
-import RoundTags from "@/pages/parts/RoundTags.vue";
-import SeminarContent from "@/api/inheritor/SeminarContent";
-import InheritorContent from "@/api/inheritor/InheritorContent";
-import ProjectsContent from "@/api/inheritor/ProjectsContent";
-import IntroBlock from "@/pages/article/common/IntroBlock.vue";
-import DetailTabPage, { type DetailTabPageTabsArray } from "@/pages/article/common/DetailTabPage.vue";
-import CommonListPage from "@/pages/article/common/CommonListPage.vue";
-import Parse from "@/components/display/parse/Parse.vue";
-import Image from "@/components/basic/Image.vue";
-
-const { nextId } = useTabId({ idStart: 5 });
-const TAB_ID_PRIZE = nextId();
-const TAB_ID_ASSOCIATION_ME = nextId();
-const TAB_ID_ICH_SITES = nextId();
-
-async function load(id: number, tabsArray: DetailTabPageTabsArray) {
-  const d = await InheritorContent.getContentDetail(id);
-  tabsArray.getTabById(TAB_ID_PRIZE)!.visible = Boolean(d.prize);
-  tabsArray.getTabById(TAB_ID_ASSOCIATION_ME)!.visible = Boolean(d.associationMeList && d.associationMeList.length > 0);
-  tabsArray.getTabById(TAB_ID_ICH_SITES)!.visible = Boolean(d.ichSitesList && (d.ichSitesList as any[]).length > 0);
-  d.titleBox = Boolean(d.modelId === InheritorContent.modelId && d.deathBirth);
-  return d;
-}
-async function loadSubList(page: number, pageSize: number, content: any, subList: string) {
-  const list = (content[subList] as any[] || [])
-    .slice((page - 1) * pageSize, page * pageSize);
-
-  list.forEach((p) => {
-    p.bottomTags = [
-      p.levelText,
-      p.ichTypeText,
-      p.batchText
-    ];
-  });
-  return {
-    list,
-    total: list.length,
-  }
-}
-
-
-const pageRef = ref();
-
-onShareTimeline(() => {
-  return pageRef.value?.getPageShareData() || {}; 
-})
-onShareAppMessage(() => {
-  return pageRef.value?.getPageShareData() || {};
-})
-
-</script>
-
-<style lang="scss">
-
-</style>

+ 1 - 1
src/pages/inhert/inheritor/list.vue

@@ -2,7 +2,7 @@
   <CommonListPage 
   <CommonListPage 
     title="非遗传承人"
     title="非遗传承人"
     itemType="article-character"
     itemType="article-character"
-    detailsPage="/pages/inhert/inheritor/details"
+    detailsPage="/pages/article/data/details?pageConfigName=inheritor-details"
     showTotal
     showTotal
     :dropDownNames="dropdownNames"
     :dropDownNames="dropdownNames"
     :load="loadData" 
     :load="loadData" 

+ 2 - 2
src/pages/inhert/map/index.vue

@@ -212,8 +212,8 @@ function onMarkerTap(e: { markerId: number }) {
 function goDetails(id: number) {
 function goDetails(id: number) {
   switch (tabCurrentIndex.value) {
   switch (tabCurrentIndex.value) {
     case 0:
     case 0:
-    case 1: navTo('/pages/inhert/intangible/details', { id }); break;
-    case 2: navTo('/pages/inhert/artifact/details', { id }); break;
+    case 1: navTo('/pages/article/data/details?pageConfigName=intangible-details', { id }); break;
+    case 2: navTo('/pages/article/data/details?pageConfigName=artifact-details', { id }); break;
     case 3: navTo('/pages/inhert/village/details', { id }); break;
     case 3: navTo('/pages/inhert/village/details', { id }); break;
     default:
     default:
     case 4: navTo('/pages/article/details', { id }); break;
     case 4: navTo('/pages/article/details', { id }); break;

+ 1 - 1
src/pages/inhert/old/list.vue

@@ -2,7 +2,7 @@
   <CommonListPage 
   <CommonListPage 
     title="重要相关老字号"
     title="重要相关老字号"
     itemType="article-common"
     itemType="article-common"
-    detailsPage="/pages/inhert/intangible/details"
+    detailsPage="/pages/article/data/details?pageConfigName=intangible-details"
     :showTotal="false"
     :showTotal="false"
     :dropDownNames="dropdownNames"
     :dropDownNames="dropdownNames"
     :detailsParams="{
     :detailsParams="{

+ 0 - 18
src/pages/inhert/product/details.vue

@@ -1,18 +0,0 @@
-<template>
-  <DetailsCommon ref="pageRef" commonRefName="相关项目" commonRefTarget="intangible" />
-</template>
-
-<script setup lang="ts">
-import { onShareTimeline, onShareAppMessage } from '@dcloudio/uni-app';
-import { ref } from 'vue';
-import DetailsCommon from '@/pages/article/common/DetailsCommon.vue';
-
-const pageRef = ref();
-
-onShareTimeline(() => {
-  return pageRef.value?.getPageShareData() || {}; 
-})
-onShareAppMessage(() => {
-  return pageRef.value?.getPageShareData() || {};
-})
-</script>

+ 0 - 18
src/pages/inhert/seminar/details.vue

@@ -1,18 +0,0 @@
-<template>
-  <DetailsCommon ref="pageRef" commonRefName="相关项目" commonRefTarget="intangible" />
-</template>
-
-<script setup lang="ts">
-import { onShareTimeline, onShareAppMessage } from '@dcloudio/uni-app';
-import { ref } from 'vue';
-import DetailsCommon from '@/pages/article/common/DetailsCommon.vue';
-
-const pageRef = ref();
-
-onShareTimeline(() => {
-  return pageRef.value?.getPageShareData() || {}; 
-})
-onShareAppMessage(() => {
-  return pageRef.value?.getPageShareData() || {};
-})
-</script>

+ 0 - 18
src/pages/inhert/songs/details.vue

@@ -1,18 +0,0 @@
-<template>
-  <DetailsCommon ref="pageRef" commonRefName="活动" commonRefTarget="product" />
-</template>
-
-<script setup lang="ts">
-import { ref } from 'vue';
-import DetailsCommon from '@/pages/article/common/DetailsCommon.vue';
-import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app';
-
-const pageRef = ref();
-
-onShareTimeline(() => {
-  return pageRef.value?.getPageShareData() || {}; 
-})
-onShareAppMessage(() => {
-  return pageRef.value?.getPageShareData() || {};
-})
-</script>

+ 0 - 67
src/pages/introduction/custom/list.vue

@@ -1,67 +0,0 @@
-<template>
-  <CommonListPage 
-    title="闽南民俗"
-    itemType="image-large-2"
-    detailsPage="custom"
-    showTotal
-    :dropDownNames="dropdownNames"
-    :load="loadData" 
-    @goCustomDetails="handleDetils"
-  />
-  <!--  -->
-</template>
-
-<script setup lang="ts">
-import { ref } from 'vue';
-import CommonListPage, { type DropDownNames } from '@/pages/article/common/CommonListPage.vue';
-import CommonContent, { GetContentListParams } from '@/api/CommonContent';
-import ProjectsContent from '@/api/inheritor/ProjectsContent';
-import { navTo } from '@/components/utils/PageAction';
-
-const dropdownNames = ref<DropDownNames[]>([]);
-
-async function loadData(
-  page: number, 
-  pageSize: number,
-  searchText: string,
-  dropDownValues: number[],
-  tabSelect: number,
-) {
-  const res = await CommonContent.getContentList(new GetContentListParams()
-    .setKeywords(searchText)
-    .setModelId(4)
-    .setMainBodyColumnId([ 245, 248 ])
-  , page, pageSize);
-  res.list.forEach((item) => {
-    item.pageType = 'news';
-  });
-
-  (await ProjectsContent.getContentList(new GetContentListParams()
-    .setKeywords('民俗 ' + searchText)
-  , 1, 20)).list.forEach((item) => {
-    item.pageType = 'intangible';
-    item.bottomTags = [
-      item.levelText, 
-      item.ichTypeText, 
-      item.batchText,
-      item.regionText,
-    ]
-    res.list.push(item);
-  });
-  return { list: res.list, total: res.total }
-}
-
-function handleDetils(item: any) {
-  if (item.pageType == 'news') {
-    navTo('/pages/article/details', {
-      modelId: item.modelId,
-      mainBodyColumnId: item.mainBodyColumnId,
-      id: item.id,
-    });
-  } else {
-    navTo('/pages/inhert/intangible/details', {
-      id: item.id,
-    });
-  }
-}
-</script>

+ 0 - 112
src/pages/introduction/news.vue

@@ -1,112 +0,0 @@
-
-<template>
-  <CommonRoot>
-    <FlexCol :padding="30" innerClass="bg-base">
-      <FlexRow>
-        <Touchable
-          direction="row"
-          width="450"
-          align="center"
-          :activeOpacity="1"
-          @click="($refs.pickerField as any).show()"
-        >
-          <Text>筛选日期:</Text>
-          <PickerField 
-            ref="pickerField"
-            v-model="filterDate"
-            placeholder="选择日期"
-            title="筛选日期"
-            :columns="[filterDates]" 
-          />
-          <Icon name="arrow-down" />
-        </Touchable>
-        <SearchBar
-          v-model="searchText"
-          inputBackgroundColor="transparent"
-          leftIcon=""
-          placeholder="搜索新闻"
-          cancelState="hidden"
-          searchState="show"
-          @search="loadNews"
-        />
-      </FlexRow>
-      <Box2LineImageRightShadow 
-        v-for="(item, i) in newsLoader.list.value"
-        :key="item.id"
-        :class="[
-          'position-relative d-flex flex-grow-1',
-        ]"
-        class="w-100"
-        titleColor="title-text"
-        :image="item.thumbnail || item.image"
-        :title="item.title"
-        :desc="item.desc"
-        :badge="item.badge"
-        :wideImage="true"
-        @click="goDetails(item, item.id)"
-      />
-      <SimplePageListLoader :loader="newsLoader" />
-    </FlexCol>
-  </CommonRoot>
-</template>
-
-<script setup lang="ts">
-import { onMounted, ref, watch } from 'vue';
-import { type GetContentListItem, GetContentListParams } from '@/api/CommonContent';
-import type { PickerItem } from '@/components/form/Picker';
-import { useSimplePageListLoader } from '@/common/composeabe/SimplePageListLoader';
-import { navCommonDetail } from '../article/common/CommonContent';
-import NewsIndexContent from '@/api/news/NewsIndexContent';
-import SimplePageListLoader from '@/common/components/SimplePageListLoader.vue';
-import CommonRoot from '@/components/dialog/CommonRoot';
-import FlexCol from '@/components/layout/FlexCol.vue';
-import SearchBar from '@/components/form/SearchBar.vue';
-import Box2LineImageRightShadow from '../parts/Box2LineImageRightShadow.vue';
-import FlexRow from '@/components/layout/FlexRow.vue';
-import Touchable from '@/components/feedback/Touchable.vue';
-import PickerField from '@/components/form/PickerField.vue';
-import Text from '@/components/basic/Text.vue';
-import Icon from '@/components/basic/Icon.vue';
-
-const searchText = ref('');
-const filterDate = ref(['']);
-
-const newsLoader = useSimplePageListLoader(10, async (page, pageSize) => {
-  return await NewsIndexContent.getContentList(new GetContentListParams()
-    .setMainBodyColumnId([ 228,298,299 ])
-    .setKeywords(searchText.value)
-    .setSelfValues({
-      publishAt: filterDate.value[0],
-    })
-  , page, pageSize);
-});
-
-const nowYear = new Date().getFullYear();
-const filterDates : PickerItem[] = [
-  { text: '全部', value: 0 },
-  ...(new Array(21).fill(0).map((_, index) => {
-    return { 
-      text: `${nowYear - index}`, 
-      value: nowYear - index 
-    }
-  }))
-];
-
-function loadNews() {
-  newsLoader.loadData(undefined, true);
-}
-function goDetails(item: GetContentListItem, id: number) {
-  navCommonDetail({
-    id,
-    mainBodyColumnId: item.mainBodyColumnId,
-    modelId: item.modelId,
-  });
-}
-
-watch(filterDate, () => {
-  loadNews();
-});
-onMounted(() => {
-  loadNews();
-});
-</script>

+ 3 - 3
src/pages/user/collect/index.vue

@@ -75,13 +75,13 @@ function goCustomDetails(item: any) {
       navTo('/pages/video/details', params)
       navTo('/pages/video/details', params)
       break;
       break;
     case 'artifact':
     case 'artifact':
-      navTo('/pages/inhert/artifact/details', params)
+      navTo('/pages/article/data/details?pageConfigName=artifact-details', params)
       break;
       break;
     case 'intangible':
     case 'intangible':
-      navTo('/pages/inhert/intangible/details', params)
+      navTo('/pages/article/data/details?pageConfigName=intangible-details', params)
       break;
       break;
     case 'inheritor':
     case 'inheritor':
-      navTo('/pages/inhert/inheritor/details', params)
+      navTo('/pages/article/data/details?pageConfigName=inheritor-details', params)
       break;
       break;
 
 
   }
   }