Kaynağa Gözat

📦 通用内容列表页核心

快乐的梦鱼 1 hafta önce
ebeveyn
işleme
189420400f

+ 7 - 2
src/App.vue

@@ -5,10 +5,12 @@ import { configTheme } from './components/theme/ThemeDefine';
 import { RequestApiConfig } from '@imengyu/imengyu-utils';
 import ApiCofig from './common/config/ApiCofig';
 import { isDev } from './common/config/AppCofig';
+import { useCommonCategoryGlobalLoader } from './pages/article/data/CommonCategoryGlobalLoader';
 
 const authStore = useAuthStore();
+const { loadCommonCategory } = useCommonCategoryGlobalLoader();
 
-onLaunch(() => {
+onLaunch(async () => {
   console.log('App Launch');
 
   uni.loadFontFace({
@@ -22,7 +24,8 @@ onLaunch(() => {
     source: 'url("https://mncdn.wenlvti.net/app_static/minnan/fonts/HUN-din1451.woff")',
   });
 
-  authStore.loadLoginState();
+  await authStore.loadLoginState();
+  await loadCommonCategory();
 })
 
 RequestApiConfig.setConfig({
@@ -30,6 +33,8 @@ RequestApiConfig.setConfig({
   BaseUrl: isDev ? ApiCofig.server.Dev : ApiCofig.server.Prod,
 })
 
+
+
 configTheme(false, (theme, darkTheme) => {
   theme.colorConfigs.default.primary = '#d9492e';
   theme.colorConfigs.pressed.primary = '#882d1d';

+ 10 - 1
src/api/CommonContent.ts

@@ -56,7 +56,16 @@ export class GetContentListParams extends DataModel<GetContentListParams> {
   }
 
 
-  setMainBodyColumnId(val: number|number[]) {
+  setMainBodyColumnId(val: string|number|number[]|undefined) {
+    if (val === undefined) 
+      return this;
+    if (typeof val === 'string') {
+      if (val.includes(',')) {
+        val = val.split(',').map((item) => parseInt(item));
+      } else {
+        val = parseInt(val);
+      }
+    }
     this.mainBodyColumnId = val;
     return this;
   }

+ 4 - 0
src/common/composeabe/LoadQuerys.ts

@@ -17,6 +17,10 @@ export function useLoadQuerys<T extends Record<string, any>>(
       for (const key in querys.value) {
         if (typeof defaults[key] === 'number')
           (querys.value as Record<string, any>)[key] = Number(_querys[key]); 
+        else if (typeof defaults[key] === 'boolean')
+          (querys.value as Record<string, any>)[key] = Boolean(_querys[key]); 
+        else if (typeof defaults[key] === 'object')
+          (querys.value as Record<string, any>)[key] = JSON.parse(_querys[key]); 
         else
           querys.value[key] = _querys[key];
       }

+ 7 - 7
src/common/composeabe/SimpleDataLoader.ts

@@ -47,13 +47,13 @@ export function useSimpleDataLoader<T, P = any>(
     }
   }
 
-  onMounted(() => {
-    if (loadWhenMounted) {
-      setTimeout(() => {
-        loadData();
-      }, (0.5 + Math.random()) * 500);
-    }
-  })
+  if (loadWhenMounted) {
+    onMounted(() => {
+        setTimeout(() => {
+          loadData();
+        }, (0.5 + Math.random()) * 500);
+    })
+  }
 
   return {
     content,

+ 1 - 0
src/common/config/ApiCofig.ts

@@ -7,6 +7,7 @@ export default {
     Dev: 'https://mn.wenlvti.net/api',
     Prod: 'https://mn.wenlvti.net/api',
   },
+  dynamicCategoryConfigServer: 'https://mn.wenlvti.net/app_static/dynamicCategoryConfig.json',
   mainBodyId: 1,
   platformId: 327,
   /**

+ 6 - 20
src/pages.json

@@ -34,20 +34,6 @@
       }
     },
     {
-      "path": "pages/introduction/communicate",
-      "style": {
-        "navigationBarTitleText": "世界走透透",
-        "enablePullDownRefresh": true
-      }
-    },
-    {
-      "path": "pages/introduction/explore",
-      "style": {
-        "navigationBarTitleText": "闽南百科",
-        "enablePullDownRefresh": true
-      }
-    },
-    {
       "path": "pages/introduction/inhert",
       "style": {
         "navigationBarTitleText": "遗产报你知"
@@ -232,6 +218,12 @@
       }
     },
     {
+      "path": "pages/article/data/list",
+      "style": {
+        "navigationBarTitleText": "动态通用列表页"
+      }
+    },
+    {
       "path": "pages/article/details",
       "style": {
         "navigationBarTitleText": "新闻详情",
@@ -330,12 +322,6 @@
       }
     },
     {
-      "path": "pages/research/index",
-      "style": {
-        "navigationBarTitleText": "文化新视角"
-      }
-    },
-    {
       "path": "pages/document/details",
       "style": {
         "navigationBarTitleText": "文档详情"

+ 119 - 57
src/pages/article/common/CommonCategoryHome.vue

@@ -9,7 +9,10 @@
         moreText="更多"
         @clickMore="category.morePage" 
       />
-      <SimplePageContentLoader :loader="category.data" >
+      <template v-if="category.type === 'CalendarBlock'">
+        <CalendarBlock />
+      </template>
+      <SimplePageContentLoader v-else-if="category.data" :loader="category.data" >
         <FlexCol>
           <template v-if="category.type === 'article'">
             <Box2LineRightShadow
@@ -40,6 +43,39 @@
               </FlexRow>
             </scroll-view>
           </template>
+          <template v-else-if="category.type === 'horizontal-large'">
+            <scroll-view scroll-x>
+              <view class="pb-3 pt-3 d-flex flex-row overflow-visible align-stretch">
+                <Box2LineLargeImageUserShadow
+                  v-for="(item, i) in category.data.content.value"
+                  classNames="width-2-3 mr-2"
+                  titleColor="title-text"
+                  title1
+                  fixSize
+                  :key="i"
+                  :title="item.title"
+                  :desc="item.desc"
+                  :image="item.thumbnail || item.image"
+                  @click="category.detailPage(item)"
+                />
+              </view>
+            </scroll-view>
+          </template>
+          <template v-else-if="category.type === 'large-grid2'">
+            <FlexRow wrap align="stretch" justify="space-between" overflow="visible">
+              <Box2LineLargeImageUserShadow
+                v-for="(item, i) in category.data.content.value"
+                titleColor="title-text"
+                width="calc(50% - 10rpx)"
+                fixSize
+                :key="i"
+                :title="item.title"
+                :desc="item.desc"
+                :image="item.image"
+                @click="category.detailPage(item)"
+              />
+            </FlexRow>
+          </template>
           <template v-else>
             <Box2LineImageRightShadow
               v-for="(item, i) in category.data.content.value"
@@ -60,24 +96,25 @@
 </template>
 
 <script setup lang="ts">;
+import { type PropType } from 'vue';
+import { CommonContentApi, GetContentListItem, GetContentListParams } from '@/api/CommonContent';
+import { navCommonDetail, navCommonList, resolveCommonContentFormData, resolveCommonContentGetPageDetailUrlAuto, useHomeCommonCategoryBlock, type HomeCommonCategoryBlockProps, type IHomeCommonCategoryBlock } from './CommonContent';
+import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import { navTo } from '@/components/utils/PageAction';
+import { DateUtils } from '@imengyu/imengyu-utils';
 import HomeTitle from '@/pages/parts/HomeTitle.vue';
 import SimplePageContentLoader from '@/common/components/SimplePageContentLoader.vue';
 import Box2LineImageRightShadow from '@/pages/parts/Box2LineImageRightShadow.vue';
 import Box2LineRightShadow from '@/pages/parts/Box2LineRightShadow.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
-import type { PropType } from 'vue';
-import { CommonContentApi, GetContentListItem, GetContentListParams } from '@/api/CommonContent';
-import { navCommonDetail, navCommonList, resolveCommonContentFormData, resolveCommonContentGetPageDetailUrlAuto, type IHomeCommonCategoryBlock } from './CommonContent';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
-import { navTo } from '@/components/utils/PageAction';
-import { DateUtils } from '@imengyu/imengyu-utils';
 import FlexRow from '@/components/layout/FlexRow.vue';
+import CalendarBlock from '@/pages/travel/calendar/block.vue';
 import Box2LineLargeImageUserShadow from '@/pages/parts/Box2LineLargeImageUserShadow.vue';
 
 export interface CategoryDefine {
   title: string;
-  content: CommonContentApi|IHomeCommonCategoryBlock;
-  type?: 'article'|'large-image2'|''|undefined;
+  content: CommonContentApi|IHomeCommonCategoryBlock|HomeCommonCategoryBlockProps|null;
+  type?: 'article'|'large-image2'|'horizontal-large'|'large-grid2'|'CalendarBlock'|undefined;
   detailPage?: string;
   morePage?: string;
   noFrom?: boolean;
@@ -85,61 +122,86 @@ export interface CategoryDefine {
 }
 
 const props = defineProps({
+  /**
+   * 分类定义。仅支持初始化后立即使用,后续修改不会生效。
+   */
   categoryDefine: {
     type: Array as PropType<CategoryDefine[]>,
     default: () => [],
   }
 });
 
-const categoryDatas = props.categoryDefine.map(item => ({
-  ...item,
-  detailPage: (dataItem: GetContentListItem) => {
-    const id = dataItem.id;
-    if (item.content instanceof CommonContentApi) {
-      if (item.detailPage) {
-        if (item.detailPage === 'byContent')
-          navTo(resolveCommonContentGetPageDetailUrlAuto(dataItem), { id });
+const categoryDatas = props.categoryDefine.map(item => { 
+  if (!item.content)
+    return {
+      ...item,
+      detailPage: () => {},
+      morePage: () => {
+        if (item.morePage) {
+          navTo(item.morePage, {});
+        }
+      },
+      data: null,
+    };
+  if (item.content instanceof CommonContentApi) {
+    return {
+      ...item,
+      detailPage: (dataItem: GetContentListItem) => {
+        const id = dataItem.id;
+        const content = item.content as CommonContentApi;
+        if (item.detailPage) {
+          if (item.detailPage === 'byContent')
+            navTo(resolveCommonContentGetPageDetailUrlAuto(dataItem), { id });
+          else
+            navTo(item.detailPage, { id });
+        } else {
+          navCommonDetail({
+            id,
+            mainBodyColumnId: content.mainBodyColumnId,
+            modelId: content.modelId,
+          })
+        }
+      },
+      morePage: () => {
+        const content = item.content as CommonContentApi;
+        if (item.morePage) {
+          navTo(item.morePage, {});
+        } else {
+          navCommonList({
+            title: item.title,
+            mainBodyColumnId: content.mainBodyColumnId,
+            modelId: content.modelId,
+            detailsPage: item.detailPage,
+          })
+        }
+      },
+      data: useSimpleDataLoader(async () => {
+        let res = (await (item.content as CommonContentApi)
+          .getContentList(new GetContentListParams(), 1, 3))
+          .list;
+        if (!item.noFrom)
+          res = resolveCommonContentFormData(res);
         else
-          navTo(item.detailPage, { id });
-      } else {
-        navCommonDetail({
-          id,
-          mainBodyColumnId: item.content.mainBodyColumnId,
-          modelId: item.content.modelId,
-        })
-      }
-    } else {
-      item.content.goDetail(dataItem);
+          res.forEach(p => {
+            if (!p.desc)
+              p.desc = DateUtils.formatDate(p.publishAt, 'YYYY-MM-dd');
+          })
+        return res;
+      })
     }
-  },
-  morePage: () => {
-    if (item.content instanceof CommonContentApi) {
-      if (item.morePage) {
-        navTo(item.morePage, {});
-      } else {
-        navCommonList({
-          title: item.title,
-          mainBodyColumnId: item.content.mainBodyColumnId,
-          modelId: item.content.modelId,
-          detailsPage: item.detailPage,
-        })
-      }
-    } else {
-      item.content.goList();
+  } else {
+    const block = item.content.type === 'CommonCategoryBlock' ? 
+      item.content : 
+      useHomeCommonCategoryBlock({
+        ...item.content,
+        resolveData: item.noFrom ? undefined : resolveCommonContentFormData,
+      });
+    return {
+      ...item,
+      detailPage: block.goDetail,
+      morePage: block.goList,
+      data: block.loader,
     }
-  },
-  data: item.content instanceof CommonContentApi ? useSimpleDataLoader(async () => {
-    let res = (await (item.content as CommonContentApi)
-      .getContentList(new GetContentListParams(), 1, 3))
-      .list;
-    if (!item.noFrom)
-      res = resolveCommonContentFormData(res);
-    else
-      res.forEach(p => {
-        if (!p.desc)
-          p.desc = DateUtils.formatDate(p.publishAt, 'YYYY-MM-dd');
-      })
-    return res;
-  }) : item.content.loader,
-}));
+  }
+});
 </script>

+ 14 - 11
src/pages/article/common/CommonContent.ts

@@ -7,6 +7,7 @@ import { navTo } from "@/components/utils/PageAction";
  */
 
 export interface IHomeCommonCategoryBlock {
+  type: 'CommonCategoryBlock',
   loader: ISimpleDataLoader<GetContentListItem[], any>;
   goDetail: (i: GetContentListItem) => void;
   goList: () => void; 
@@ -69,21 +70,24 @@ export function resolveCommonContentGetPageDetailUrlAuto(item: GetContentListIte
   return '/pages/article/details';
 }
 
-/**
- * 专用于通用内容的首页小列表控制代码组合
- * @param p 
- * @returns 
- */
-export function useHomeCommonCategoryBlock(p: {
+export interface HomeCommonCategoryBlockProps {
   title?: string,
-  mainBodyColumnId?: number|number[],
+  type?: '',
+  mainBodyColumnId?: string|number|number[],
   modelId?: number,
   itemType?: string,
   detailsPage: string,
   count?: number,
   params?: Record<string, any>,
   resolveData?: (item: GetContentListItem[]) => GetContentListItem[],
-}) : IHomeCommonCategoryBlock {
+}
+
+/**
+ * 专用于通用内容的首页小列表控制代码组合
+ * @param p 
+ * @returns 
+ */
+export function useHomeCommonCategoryBlock(p: HomeCommonCategoryBlockProps, loadWhenMounted = true) : IHomeCommonCategoryBlock {
   function goDetail(i: GetContentListItem) {
     navTo(p.detailsPage === 'byContent' ? 
       resolveCommonContentGetPageDetailUrlAuto(i): 
@@ -105,7 +109,6 @@ export function useHomeCommonCategoryBlock(p: {
       detailsPage: p.detailsPage,
     }) 
   }
-
   const loader = useSimpleDataLoader(async () => {
     let res = (await CommonContent.getContentList(new GetContentListParams().setSelfValues({
       mainBodyColumnId: p.mainBodyColumnId,
@@ -115,9 +118,9 @@ export function useHomeCommonCategoryBlock(p: {
     if (p.resolveData)
       res = p.resolveData(res);
     return res;
-  });
-
+  }, loadWhenMounted);
   return {
+    type: 'CommonCategoryBlock',
     loader,
     goDetail,
     goList,

+ 91 - 99
src/pages/article/common/CommonListPage.vue

@@ -59,8 +59,8 @@
     
     <!-- 列表 -->
     <slot name="list" :tabId="tabCurrentId" />
-    <view class="position-relative d-flex flex-row flex-wrap justify-between align-stretch mt-3">
-      <template v-if="showList">
+    <template v-if="showList">
+      <view class="position-relative d-flex flex-row flex-wrap justify-between align-stretch mt-3">
         <view
           v-for="(item, i) in listLoader.list.value"
           :key="item.id"
@@ -130,9 +130,9 @@
 
         </view>
         <view v-if="itemType.endsWith('-2') && listLoader.list.value.length % 2 != 0" class="width-1-2" />
-      </template>
-    </view>
-    <SimplePageListLoader :loader="listLoader" />
+      </view>
+      <SimplePageListLoader :loader="listLoader" />
+    </template>
   </view>
 </template>
 
@@ -147,6 +147,7 @@ import SimpleDropDownPicker, { type SimpleDropDownPickerItem } from '@/common/co
 import AppCofig from '@/common/config/AppCofig';
 import Tabs from '@/components/nav/Tabs.vue';
 import SearchBar from '@/components/form/SearchBar.vue';
+import { resolveCommonContentGetPageDetailUrlAuto } from './CommonContent';
 
 function getImage(item: any) {
   return item.thumbnail || item.image || AppCofig.defaultImage
@@ -174,81 +175,61 @@ export interface CommonListItem extends Record<string, any>  {
   image: string,
   title: string,
 }
-
-const props = defineProps({
+export interface CommonListPageProps {
   /** 
    * 标题
    */
-  title: {
-    type: String,
-    default: '',
-  },
+  title?: string
   /**
    * 分组标签
    */
-  tabs: {
-    type: Array as PropType<{ 
-      id: number, 
-      text: string,
-      onlyJump?: boolean,
-      jump?: () => void,
-      width?: number,
-    }[]>,
-    default: null,
-  },
-  tabsScrollable: {
-    type: Boolean,
-    default: false, 
-  },
+  tabs?: { 
+    id: number, 
+    text: string,
+    onlyJump?: boolean,
+    jump?: () => void,
+    width?: number,
+  }[] | null
+  /**
+   * 标签是否可滚动
+   * @default false
+   */
+  tabsScrollable?: boolean
   /**
    * 是否显示搜索框
+   * @default true
    */
-  showSearch: {
-    type: Boolean,
-    default: true,
-  },
+  showSearch?: boolean
   /**
    * 是否显示Tab
+   * @default true
    */
-  showTab: {
-    type: Boolean,
-    default: true,
-  },
+  showTab?: boolean
   /**
    * 显示总数
+   * @default false
    */
-  showTotal: {
-    type: Boolean,
-    default: false,
-  },
+  showTotal?: boolean
   /**
    * 显示列表的Tab ID。默认所有Tab都显示列表。
+   * @default undefined
    */
-  showListTabIds: {
-    type: Array as PropType<number[]>,
-    default: () => ([]),
-  },
+  showListTabIds?: number[]
   /**
    * 下拉框选项控制
+   * @default []
    */
-  dropDownNames: {
-    type: Object as PropType<DropDownNames[]>,
-    default: () => ([]),
-  },
+  dropDownNames?: DropDownNames[]
   /**
    * 列表项类型
+   * @default 'article-common'
    */
-  itemType: {
-    type: String as PropType<'image-large-2'|'image-large'|'article-common'|'article-character'|'simple-text'>,
-    default: 'article-common',
-  },
+  itemType?: 'image-large-2'|'image-large'|'article-common'|'article-character'|'simple-text'
   /**
    * 分页大小
+   * @default 8
    */
-  pageSize: {
-    type: Number,
-    default: 8,
-  },
+  pageSize?: number
   /**
    * 加载数据函数
    * @param page 页码,从1开始
@@ -256,51 +237,57 @@ const props = defineProps({
    * @param searchText 搜索文本
    * @param dropDownValues 下拉框值
    */
-  load: {
-    type: Function as PropType<(
-      page: number, 
-      pageSize: number,
-      searchText: string,
-      dropDownValues: number[],
-      tabSelect: number,
-    ) => Promise<{ list: CommonListItem[], total: number }>>,
-    required: true,
-  },
+  load: (page: number, pageSize: number, searchText: string, dropDownValues: number[], tabSelect: number) => Promise<{ list: CommonListItem[], total: number }>
   /**
    * 点击详情跳转页面路径
    * 可以是字符串路径,也可以是对象数组,每个对象包含路径和参数
    * * 特殊值:byContent 表示根据 detailsPageByContentCallback 函数返回值跳转。
    */
-  detailsPage: {
-    type: [String,Object],
-    default: '/pages/article/details'
-  },
+  detailsPage?: string | Record<string, string|{
+    page: string,
+    params: Record<string, any>,
+  }>
   /**
    * 根据内容项返回跳转路径的回调函数
    */
-  detailsPageByContentCallback: {
-    type: Function as PropType<(item: any) => string>,
-    default: true,
-  },
+  detailsPageByContentCallback?: (item: any) => string
   /**
    * 详情跳转页面参数
    */
-  detailsParams: {
-    type: Object as PropType<Record<string, any>>,
-    default: () => ({})
-  },
-  hasBg: {
-    type: Boolean,
-    default: true,
-  },
-  startTabIndex: {
-    type: Number,
-    default: undefined, 
-  },
-  loadMounted: {
-    type: Boolean,
-    default: true,
-  },
+  detailsParams?: Record<string, any>
+  /**
+   * 是否有背景
+   * @default true
+   */
+  hasBg?: boolean
+  /**
+   * 起始标签索引
+   * @default 0
+   */
+  startTabIndex?: number | undefined
+  /**
+   * 挂载时是否加载数据
+   * @default true
+   */
+  loadMounted?: boolean
+}
+
+const props = withDefaults(defineProps<CommonListPageProps>(), {
+  title: '',
+  tabs: null,
+  tabsScrollable: false,
+  showSearch: true,
+  showTab: true,
+  showTotal: false,
+  dropDownNames: () => [],
+  detailsPageByContentCallback: resolveCommonContentGetPageDetailUrlAuto,
+  itemType: 'article-common',
+  pageSize: 8,
+  detailsPage: '/pages/article/details',
+  detailsParams: () => ({}),
+  hasBg: true,
+  startTabIndex: undefined,
+  loadMounted: true,
 })
 
 const emit = defineEmits([ 'goCustomDetails' ])
@@ -325,7 +312,7 @@ const listLoader = useSimplePageListLoader(props.pageSize, async (page, pageSize
 });
 const tabCurrentIndex = ref(0)
 const tabCurrentId = computed(() => props.tabs?.[tabCurrentIndex.value]?.id ?? -1)
-const showList = computed(() => props.showListTabIds.length == 0 || props.showListTabIds.includes(tabCurrentId.value))
+const showList = computed(() => !props.showListTabIds || props.showListTabIds.includes(tabCurrentId.value))
 
 function handleChangeDropDownValue(index: number, value: number) {
   dropDownValues.value[index] = value;
@@ -356,7 +343,7 @@ function goDetails(item: any, id: number) {
       return;
   }
   function handleByContent() {
-    const page = props.detailsPageByContentCallback(item);
+    const page = props.detailsPageByContentCallback?.(item);
     if (page) {
       navTo(page, { 
         ...props.detailsParams, 
@@ -367,19 +354,24 @@ function goDetails(item: any, id: number) {
     return false;
   }
 
-  if (typeof props.detailsPage === 'object' && typeof props.detailsPage[0] === 'string') {
-    if (props.detailsPage[tabCurrentIndex.value] == 'byContent' && handleByContent())
+  const page = props.detailsPage === 'object' ? props.detailsPage[tabCurrentIndex.value] : undefined;
+
+  if (typeof page === 'string') {
+    if (page == 'byContent' && handleByContent())
       return;
-    navTo(props.detailsPage[tabCurrentIndex.value], { 
+    navTo(page, { 
       ...props.detailsParams, 
       id 
     })
     return; 
   }
-  if (typeof props.detailsPage == 'object' && typeof props.detailsPage[0] === 'object') {
-    if (props.detailsPage[tabCurrentIndex.value].page == 'byContent' && handleByContent())
+  if (typeof page === 'object') {
+    const item = page as {
+      page: string,
+      params: Record<string, any>,
+    };
+    if (item.page == 'byContent' && handleByContent())
       return;
-    const item = props.detailsPage[tabCurrentIndex.value];
     navTo(item.page, { 
       ...item.params, 
       id 
@@ -424,11 +416,11 @@ onMounted(() => {
   if (props.title)
     uni.setNavigationBarTitle({ title: props.title, })
   loadDropDownValues();
-  if (props.loadMounted) {
-    setTimeout(() => {
+  setTimeout(() => {
+    if (props.loadMounted && showList.value) {
       listLoader.loadData(undefined, true);
-    }, 500);
-  }
+    }
+  }, 500);
 });
 </script>
 

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

@@ -0,0 +1,82 @@
+import { useHomeCommonCategoryBlock, type HomeCommonCategoryBlockProps } from "../common/CommonContent";
+import type { CommonListPageProps } from "../common/CommonListPage.vue";
+import { CommonCategoryDynamicDataSerializedApi, type IHomeCommonCategoryDynamicData } from "./CommonCategoryDynamicData";
+
+//基础定义
+
+export interface IHomeCommonCategoryDefine {
+  /**
+   * 通用页面定义
+   */
+  page: {
+    name: string,
+    title: string,
+    content: IHomeCommonCategoryListDefine,
+  }[],
+}
+
+// 列表定义
+
+export interface IHomeCommonCategoryListDefine {
+  type: 'CommonList',
+  props: Omit<CommonListPageProps, 'load'|'tabs'> & {
+    tabs?: (IHomeCommonCategoryListTabDefine & {
+      text: string,
+      width?: number,
+      visible?: boolean,
+    })[],
+    noFrom?: boolean,
+    data?: IHomeCommonCategoryDynamicData,
+  },
+}
+export interface IHomeCommonCategoryListTabListDefine {
+  type: 'list',
+  data: IHomeCommonCategoryDynamicData,
+}
+export interface IHomeCommonCategoryListTabJumpDefine {
+  type: 'jump',
+  url: string,
+  params?: Record<string, any>,
+}
+export interface IHomeCommonCategoryListTabNestCategoryDefine {
+  type: 'nestCategory',
+  categorys: {
+    visible?: boolean,
+    text: string,
+    type: string, 
+    itemType?: string,
+    data: IHomeCommonCategoryDynamicData,
+    morePage?: string,
+    detailPage?: string,
+    count?: number,
+    noFrom?: boolean,
+    params?: Record<string, any>,
+  }[],
+}
+export type IHomeCommonCategoryListTabDefine = IHomeCommonCategoryListTabListDefine 
+  | IHomeCommonCategoryListTabJumpDefine 
+  | IHomeCommonCategoryListTabNestCategoryDefine;
+
+export function CommonCategoryListTabNestCategoryDataToContent(
+  data: IHomeCommonCategoryDynamicData,
+  define: IHomeCommonCategoryListTabNestCategoryDefine['categorys'][0],
+) {
+  if (!data)
+    return null;
+  switch (data.type) {
+    case 'serializedApi':
+      return CommonCategoryDynamicDataSerializedApi(data);
+    case 'request':
+      throw new Error(`未实现的动态数据接口 ${data.type}`);
+    case 'commonContent':
+      return {
+        title: define.text,
+        mainBodyColumnId: data.params.mainBodyColumnId,
+        modelId: data.params.modelId,
+        itemType: define.itemType,
+        detailsPage: define.detailPage || 'byContent',
+        count: define.count,
+        params: define.params,
+      } as HomeCommonCategoryBlockProps;
+  }
+}

+ 120 - 0
src/pages/article/data/CommonCategoryDynamicData.ts

@@ -0,0 +1,120 @@
+
+// 动态数据接口定义
+
+import CommonContent, { GetContentListParams } from "@/api/CommonContent";
+import CalendarContent from "@/api/fusion/CalendarContent";
+import ScenicSpotContent from "@/api/fusion/ScenicSpotContent";
+import ActivityContent from "@/api/inheritor/ActivityContent";
+import InheritorContent from "@/api/inheritor/InheritorContent";
+import MoveableContent from "@/api/inheritor/MoveableContent";
+import ProductsContent from "@/api/inheritor/ProductsContent";
+import ProjectsContent from "@/api/inheritor/ProjectsContent";
+import SeminarContent from "@/api/inheritor/SeminarContent";
+import UnitContent from "@/api/inheritor/UnitContent";
+import UnmoveableContent from "@/api/inheritor/UnmoveableContent";
+import BulidingContent from "@/api/introduction/BulidingContent";
+import CharacterContent from "@/api/introduction/CharacterContent";
+import CustomContent from "@/api/introduction/CustomContent";
+import FeatureContent from "@/api/introduction/FeatureContent";
+import HistoryContent from "@/api/introduction/HistoryContent";
+import IndexContent from "@/api/introduction/IndexContent";
+import LanguageContent from "@/api/introduction/LanguageContent";
+import PolicyContent from "@/api/introduction/PolicyContent";
+import SeaContent from "@/api/introduction/SeaContent";
+import VictualsContent from "@/api/introduction/VictualsContent";
+import NotConfigue from "@/api/NotConfigue";
+import TeamsContent from "@/api/research/TeamsContent";
+
+export interface IHomeCommonCategoryDynamicDataCommonContent {
+  type: 'commonContent',
+  url?: string,
+  params: {
+    modelId: number,
+    mainBodyColumnId?: string|number|number[],
+  },
+  otherParams?: Record<string, any>,
+}
+export interface IHomeCommonCategoryDynamicDataSerializedApi {
+  type: 'serializedApi',
+  name: string,
+  otherParams?: Record<string, any>,
+}
+export interface IHomeCommonCategoryDynamicDataRequest {
+  type: 'request',
+  method: "OPTIONS" | "GET" | "HEAD" | "POST" | "PUT" | "DELETE",
+  url: string,
+  querys?: Record<string, any>,
+  params?: Record<string, any>,
+}
+export type IHomeCommonCategoryDynamicData = IHomeCommonCategoryDynamicDataCommonContent 
+  | IHomeCommonCategoryDynamicDataSerializedApi 
+  | IHomeCommonCategoryDynamicDataRequest;
+
+export function CommonCategoryDynamicDataSerializedApi(item: IHomeCommonCategoryDynamicDataSerializedApi) {
+  switch (item.name) {
+    case 'BulidingContent': return BulidingContent;
+    case 'ActivityContent': return ActivityContent;
+    case 'InheritorContent': return InheritorContent;
+    case 'MoveableContent': return MoveableContent;
+    case 'ProductsContent': return ProductsContent;
+    case 'ProjectsContent': return ProjectsContent;
+    case 'SeminarContent': return SeminarContent;
+    case 'UnitContent': return UnitContent;
+    case 'UnmoveableContent': return UnmoveableContent;
+    case 'CalendarContent': return CalendarContent;
+    case 'ScenicSpotContent': return ScenicSpotContent;
+    case 'CharacterContent': return CharacterContent;
+    case 'FeatureContent': return FeatureContent;
+    case 'HistoryContent': return HistoryContent;
+    case 'IndexContent': return IndexContent;
+    case 'LanguageContent': return LanguageContent;
+    case 'PolicyContent': return PolicyContent;
+    case 'SeaContent': return SeaContent;
+    case 'VictualsContent': return VictualsContent;
+    case 'CustomContent': return CustomContent;
+    case 'TeamsContent': return TeamsContent;
+    default:
+      throw new Error(`未实现的序列化接口 ${item.name}`);
+  }
+}
+export async function doLoadDynamicListData(
+  item: IHomeCommonCategoryDynamicData,
+  page: number,
+  pageSize: number,
+  keywords: string,
+  dropDownValues: number[],
+) {
+  switch (item.type) {
+    default:
+      throw new Error(`未实现的动态数据接口`);
+    case 'commonContent':
+      return await CommonContent.getContentList(new GetContentListParams()
+        .setModelId(item.params.modelId)
+        .setMainBodyColumnId(item.params.mainBodyColumnId)
+        .setKeywords(keywords)
+        .setSelfValues({
+          ...item.otherParams,
+
+        }) 
+      , page, pageSize)
+      break;
+    case 'serializedApi':
+      return (await CommonCategoryDynamicDataSerializedApi(item).getContentList(new GetContentListParams()
+        .setKeywords(keywords)
+        .setSelfValues({
+          ...item.otherParams,
+        }) 
+      , page, pageSize))
+    case 'request':
+      return (await CommonContent.request(
+        item.url, 
+        { ...item.querys, page, pageSize }, 
+        {
+          method: item.method, 
+          data: item.params,
+        },
+        '',
+        undefined,
+      )).data;
+  }
+}

+ 49 - 0
src/pages/article/data/CommonCategoryGlobalLoader.ts

@@ -0,0 +1,49 @@
+import { inject, provide, ref, type Ref } from "vue";
+import { requireNotNull } from "@imengyu/imengyu-utils";
+import NotConfigue from "@/api/NotConfigue";
+import { showError } from "@/common/composeabe/ErrorDisplay";
+import type { IHomeCommonCategoryDefine } from "./CommonCategoryDefine";
+import ApiCofig from "@/common/config/ApiCofig";
+import DefaultCofig from "./DefaultCategory.json";
+import { isDev } from "@/common/config/AppCofig";
+
+// 全局加载默认分类
+
+export const COMMON_CATEGORY_KEY = 'DefaultCategory';
+
+const defaultCommonCategory = ref<IHomeCommonCategoryDefine>({
+  page: [],
+});
+
+export function injectCommonCategory() {
+  return inject(COMMON_CATEGORY_KEY, defaultCommonCategory) as Ref<IHomeCommonCategoryDefine>;
+}
+export function useCommonCategoryGlobalLoader() {
+
+  const commonCategoryData = ref<IHomeCommonCategoryDefine>(defaultCommonCategory.value);
+
+  provide(COMMON_CATEGORY_KEY, commonCategoryData);
+
+  async function loadCommonCategory() {
+    uni.showLoading({ title: '加载中' });
+    try {
+      if (isDev) {
+        commonCategoryData.value = DefaultCofig as IHomeCommonCategoryDefine;
+        return;
+      }
+      const category = (await NotConfigue.get<IHomeCommonCategoryDefine>(ApiCofig.dynamicCategoryConfigServer, '加载默认分类')).data;
+      if (category)
+        commonCategoryData.value = category;
+      else
+        showError(undefined, '默认分类未配置');
+    } catch (error) {
+      showError(error, '加载默认分类失败');
+    } finally {
+      uni.hideLoading();
+    }
+  }
+
+  return {
+    loadCommonCategory,
+  }
+}

+ 171 - 0
src/pages/article/data/CommonCategoryList.vue

@@ -0,0 +1,171 @@
+<template>
+  <FlexCol>
+    <Result v-if="errorMessage" status="error" :description="errorMessage" />
+    <CommonListPage 
+      v-else-if="currentCommonCategoryDefine"
+      v-bind="currentCommonCategoryDefine.content.props as any || undefined"
+      :title="currentCommonCategoryDefine.title || undefined"
+      :load="loadData"
+      :tabs="tabs"
+      :showListTabIds="showListTabIds"
+    >
+      <template #list="{ tabId }">
+        <CommonCategoryHome 
+          v-if="tabRenderDefines[tabId]?.type === 'nestCategory'" 
+          :categoryDefine="tabRenderDefines[tabId].categoryDefine"
+        />
+      </template>
+    </CommonListPage>
+    <Footer text="我也是有底线的~" />
+  </FlexCol>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted, ref, watch } from 'vue';
+import { injectCommonCategory } from './CommonCategoryGlobalLoader';
+import { navTo } from '@/components/utils/PageAction';
+import { doLoadDynamicListData } from './CommonCategoryDynamicData';
+import { CommonCategoryListTabNestCategoryDataToContent, type IHomeCommonCategoryDefine, type IHomeCommonCategoryListTabDefine } from './CommonCategoryDefine';
+import type { CommonListPageProps } from '../common/CommonListPage.vue';
+import CommonListPage from '../common/CommonListPage.vue';
+import Result from '@/components/feedback/Result.vue';
+import CommonCategoryHome, { type CategoryDefine } from '../common/CommonCategoryHome.vue';
+import Footer from '@/components/display/Footer.vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import { resolveCommonContentFormData } from '../common/CommonContent';
+
+/**
+ * 动态通用内容 - 通用列表页
+ */
+
+const errorMessage = ref('');
+const currentCommonCategoryDefine = ref<IHomeCommonCategoryDefine['page'][0]>();
+const commonCategory = injectCommonCategory();
+
+const props = defineProps({
+  pageConfigName: {
+    type: String,
+    default: '',
+  }
+});
+
+function loadPageConfig() {
+  if (!props.pageConfigName) {
+    errorMessage.value = '配置有误';
+    return;
+  }
+  currentCommonCategoryDefine.value = commonCategory.value.page
+    .find((item) => item.name === props.pageConfigName);
+  if (!currentCommonCategoryDefine.value) {
+    errorMessage.value = '未找到指定的分类配置:' + props.pageConfigName;
+    return;
+  }
+  uni.setNavigationBarTitle({
+    title: currentCommonCategoryDefine.value?.title || '',
+  })
+}
+
+watch(() => props.pageConfigName, loadPageConfig);
+onMounted(loadPageConfig);
+
+type RenderTabDefine = IHomeCommonCategoryListTabDefine & {
+  categoryDefine?: CategoryDefine[];  
+};
+
+const tabDefines = computed(() => currentCommonCategoryDefine.value?.content.props.tabs || []);
+const tabRenderDefines = computed(() => {
+  const result = {} as Record<number, RenderTabDefine>;
+  tabDefines.value.forEach((item, i) => {
+    const renderItem : RenderTabDefine = {
+      ...item,
+    };
+    switch (item.type) {
+      default:
+      case 'list':
+        break;
+      case 'jump':
+        break;
+      case 'nestCategory':
+        renderItem.categoryDefine = item.categorys
+          .filter((item) => item.visible !== false)
+          .map((item) => {
+            return {
+              title: item.text,
+              content: CommonCategoryListTabNestCategoryDataToContent(
+                item.data, item
+              ),
+              morePage: item.morePage,
+              detailPage: item.detailPage,
+              noFrom: item.noFrom,
+              type: item.type as CategoryDefine['type'],
+            }
+          });
+        break;
+    }
+    result[i] = renderItem;
+  });
+  return result;
+});
+const showListTabIds = computed(() => {
+  const define = tabDefines.value;
+  if (define.length === 0)
+    return undefined;
+  return define
+    .map((item, i) => ({ type: item.type, id: i }))
+    .filter((item) => item.type === 'list')
+    .map((item) => item.id);
+})
+const tabs = computed<CommonListPageProps['tabs']>(() => {
+  const define = tabDefines.value;
+  return define.map((item, i) => {
+    switch (item.type) {
+      default:
+      case 'list':
+        return {
+          id: i,
+          text: item.text,
+          width: item.width,
+        };
+      case 'jump':
+        return {
+          id: i,
+          text: item.text,
+          onlyJump: true,
+          jump: () => navTo(item.url, item.params),
+          width: item.width,
+        };
+      case 'nestCategory':
+        return {
+          id: i,
+          text: item.text,
+          width: item.width,
+        };
+    }
+  });
+});
+
+async function loadData(
+  page: number, 
+  pageSize: number,
+  searchText: string,
+  dropDownValues: number[],
+  tabSelect: number
+) {
+  const tab = tabRenderDefines.value[tabSelect];
+  if (!tab) 
+    throw new Error(`配置有误 tab:${tabSelect}`);
+  if (tab.type !== 'list')
+    throw new Error(`配置有误 tab:${tabSelect} type:${tab.type}`);
+  const res = await doLoadDynamicListData(
+    tab.data, 
+    page, 
+    pageSize, 
+    searchText, 
+    dropDownValues,
+  );
+  if (res && !currentCommonCategoryDefine.value?.content.props.noFrom) {
+    res.list = resolveCommonContentFormData(res.list);
+  }
+  return res;
+}
+</script>

+ 301 - 0
src/pages/article/data/DefaultCategory.json

@@ -0,0 +1,301 @@
+{
+  "page": [
+    {
+      "name": "explore",
+      "title": "闽南百科",
+      "content": {
+        "type": "CommonList",
+        "props": {
+          "showTab": false,
+          "tabs": [
+            {
+              "text": "Root",
+              "type": "nestCategory",
+              "categorys": [
+                {
+                  "text": "文化概况",
+                  "data": {
+                    "type": "serializedApi",
+                    "name": "HistoryContent"
+                  },
+                  "type": ""
+                },
+                {
+                  "text": "闽南百问",
+                  "detailsPage": "/pages/article/details",
+                  "itemType": "article-common",
+                  "data": {
+                    "type": "commonContent",
+                    "params": {
+                      "mainBodyColumnId": 320,
+                      "modelId": 18
+                    }
+                  },
+                  "type": "article"
+                },
+                {
+                  "text": "历史人物",
+                  "data": {
+                    "type": "serializedApi",
+                    "name": "CharacterContent"
+                  },
+                  "detailPage": "/pages/introduction/character/details",
+                  "morePage": "/pages/introduction/character/list",
+                  "type": ""
+                },
+                {
+                  "text": "民间习俗",
+                  "data": {
+                    "type": "serializedApi",
+                    "name": "CustomContent"
+                  },
+                  "morePage": "/pages/introduction/custom/list",
+                  "type": ""
+                },
+                {
+                  "text": "艺术特色",
+                  "data": {
+                    "type": "serializedApi",
+                    "name": "FeatureContent"
+                  },
+                  "type": ""
+                },
+                {
+                  "text": "政策法规",
+                  "data": {
+                    "type": "serializedApi",
+                    "name": "PolicyContent"
+                  },
+                  "type": "",
+                  "noFrom": true,
+                  "morePage": "/pages/home/laws"
+                }
+              ]
+            }
+          ]
+        }
+      }
+    },
+    {
+      "name": "research",
+      "title": "文化新视角",
+      "content": {
+        "type": "CommonList",
+        "props": {
+          "showTab": true,
+          "tabs": [
+            {
+              "text": "文化新视角",
+              "type": "nestCategory",
+              "categorys": [
+                {
+                  "text": "非遗作品秀",
+                  "data": {
+                    "type": "commonContent",
+                    "params": {
+                      "mainBodyColumnId": 365,
+                      "modelId": 18
+                    }
+                  },
+                  "detailsPage": "byContent",
+                  "type": ""
+                },
+                {
+                  "text": "非遗新青年",
+                  "data": {
+                    "type": "commonContent",
+                    "params": {
+                      "mainBodyColumnId": 364,
+                      "modelId": 18
+                    }
+                  },
+                  "detailsPage": "byContent",
+                  "type": ""
+                },
+                {
+                  "text": "非遗巴士研学",
+                  "data": {
+                    "type": "commonContent",
+                    "params": {
+                      "mainBodyColumnId": 363,
+                      "modelId": 18
+                    }
+                  },
+                  "detailsPage": "byContent",
+                  "type": ""
+                },
+                {
+                  "text": "各区重大节庆活动(一区一节)",
+                  "data": {
+                    "type": "commonContent",
+                    "params": {
+                      "mainBodyColumnId": 367,
+                      "modelId": 18
+                    }
+                  },
+                  "detailsPage": "byContent",
+                  "type": ""
+                },
+                {
+                  "text": "其他闽南文化品牌活动",
+                  "data": {
+                    "type": "commonContent",
+                    "params": {
+                      "mainBodyColumnId": 368,
+                      "modelId": 18
+                    }
+                  },
+                  "detailsPage": "byContent",
+                  "visible": false,
+                  "type": ""
+                }
+              ]
+            },
+            {
+              "text": "研究机构",
+              "type": "list",
+              "data": {
+                "type": "serializedApi",
+                "name": "TeamsContent"
+              }
+            }
+          ]
+        }
+      }
+    },
+    {
+      "name": "communicate",
+      "title": "世界走透透",
+      "content": {
+        "type": "CommonList",
+        "props": {
+          "showTab": true,
+          "tabs": [
+            {
+              "text": "世界走透透",
+              "type": "list",
+              "data": {
+                "type": "commonContent",
+                "params": {
+                  "mainBodyColumnId": [260, 261, 262],
+                  "modelId": 18
+                }
+              }
+            },
+            {
+              "text": "海洋文化",
+              "type": "list",
+              "data": {
+                "type": "serializedApi",
+                "name": "SeaContent"
+              }
+            }
+          ]
+        }
+      }
+    },
+    {
+      "name": "travel",
+      "title": "来厦门䢐迌",
+      "content": {
+        "type": "CommonList",
+        "props": {
+          "showTab": false,
+          "tabs": [
+            {
+              "text": "Root",
+              "type": "nestCategory",
+              "categorys": [
+                {
+                  "text": "闽南语在线课程",
+                  "data": {
+                    "type": "commonContent",
+                    "params": {
+                      "mainBodyColumnId": 257,
+                      "modelId": 5
+                    }
+                  },
+                  "noFrom": true,
+                  "detailsPage": "/pages/video/details",
+                  "type": "horizontal-large"
+                },
+                {
+                  "text": "闽南歌曲",
+                  "data": {
+                    "type": "commonContent",
+                    "params": {
+                      "mainBodyColumnId": [315],
+                      "modelId": 16
+                    }
+                  },
+                  "noFrom": true,
+                  "morePage": "/pages/travel/fashion/list",
+                  "detailsPage": "/pages/video/details",
+                  "type": "large-grid2"
+                },
+                {
+                  "text": "闽南节庆日历",
+                  "data": null,
+                  "morePage": "/pages/travel/calendar/index",
+                  "type": "CalendarBlock"
+                },
+                {
+                  "text": "闽南美食",
+                  "data": {
+                    "type": "commonContent",
+                    "params": {
+                      "mainBodyColumnId": 253,
+                      "modelId": 3
+                    }
+                  },
+                  "noFrom": true,
+                  "detailsPage": "byContent",
+                  "type": "large-grid2"
+                },
+                {
+                  "text": "景区、景点",
+                  "data": {
+                    "type": "commonContent",
+                    "params": {
+                      "mainBodyColumnId": 273,
+                      "modelId": 17
+                    }
+                  },
+                  "noFrom": true,
+                  "morePage": "/pages/travel/scenic-spot/list",
+                  "detailsPage": "/pages/inhert/intangible/details",
+                  "type": "large-grid2"
+                },
+                {
+                  "text": "文化旅游路线",
+                  "data": {
+                    "type": "commonContent",
+                    "params": {
+                      "mainBodyColumnId": [274,275,276,277],
+                      "modelId": 17
+                    }
+                  },
+                  "noFrom": true,
+                  "detailsPage": "byContent",
+                  "type": "horizontal-large"
+                },
+                {
+                  "text": "闽南好物",
+                  "data": {
+                    "type": "commonContent",
+                    "params": {
+                      "mainBodyColumnId": 48,
+                      "modelId": 9
+                    }
+                  },
+                  "detailsPage": "byContent",
+                  "type": "horizontal-large"
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ]
+}

+ 14 - 0
src/pages/article/data/list.vue

@@ -0,0 +1,14 @@
+<template>
+  <CommonCategoryList :pageConfigName="querys.pageConfigName" />
+</template>
+
+<script setup lang="ts">
+import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import CommonCategoryList from './CommonCategoryList.vue';
+/**
+ * 动态通用内容 - 通用列表页
+ */
+const { querys } = useLoadQuerys({
+  pageConfigName: ''
+});
+</script>

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

@@ -32,7 +32,7 @@
             title="常识一点通"
             icon="https://mncdn.wenlvti.net/app_static/minnan/images/home/IconMap.png"
             :size="50"
-            @click="navTo('/pages/introduction/explore')"
+            @click="navTo('/pages/article/data/list', { pageConfigName: 'explore' })"
           />
           <HomeButton
             title="闽南新鲜事"
@@ -50,19 +50,19 @@
             title="文化新视角"
             icon="https://mncdn.wenlvti.net/app_static/minnan/images/home/IconReserch.png"
             :size="50"
-            @click="navTo('/pages/research/index')"
+            @click="navTo('/pages/article/data/list', { pageConfigName: 'research' })"
           />
           <HomeButton
             title="世界走透透"
             icon="https://mncdn.wenlvti.net/app_static/minnan/images/home/IconArtifact.png"
             :size="50"
-            @click="navTo('/pages/introduction/communicate')"
+            @click="navTo('/pages/article/data/list', { pageConfigName: 'communicate' })"
           />
           <HomeButton
             title="来厦门䢐迌"
             icon="https://mncdn.wenlvti.net/app_static/minnan/images/home/IconDiscover.png"
             :size="50"
-            @click="navTo('/pages/introduction/travel')"
+            @click="navTo('/pages/article/data/list', { pageConfigName: 'travel' })"
           />
         </view>
 

+ 0 - 57
src/pages/introduction/communicate.vue

@@ -1,57 +0,0 @@
-<template>
-  <CommonListPage 
-    itemType="article-common"
-    :dropDownNames="dropdownNames"
-    :tabs="[
-      { id: 0, text: '世界走透透' },
-      { id: 1, text: '海洋文化' },
-    ]"
-    :startTabIndex="startTab"
-    :load="loadData" 
-  />
-</template>
-
-<script setup lang="ts">
-import { ref } from 'vue';
-import { GetContentListParams } from '@/api/CommonContent';
-import SeaContent from '@/api/introduction/SeaContent';
-import NewsIndexContent from '@/api/news/NewsIndexContent';
-import CommonListPage, { type DropDownNames } from '@/pages/article/common/CommonListPage.vue';
-import ResultContent from '@/api/research/ResultContent';
-
-const dropdownNames = ref<DropDownNames[]>([]);
-const startTab = ref(0);
-async function loadData(
-  page: number, 
-  pageSize: number,
-  searchText: string,
-  dropDownValues: number[],
-  tabSelect: number,
-) {
-  let res;
-  switch (tabSelect) {
-    case 0: 
-      res = (await NewsIndexContent.getContentList(new GetContentListParams()
-        .setKeywords(searchText)
-        .setMainBodyColumnId([260, 261, 262])
-      , page, pageSize))
-      break;
-    default:
-    case 1: 
-      res = (await SeaContent.getContentList(new GetContentListParams()
-        .setKeywords(searchText)
-      , page, pageSize));
-    break;
-  }
-  res.list.forEach((item) => {
-    item.desc = item.from ? `来源:${item.from}` : '';
-    item.bottomTags = [
-      item.levelText, 
-      item.mainBodyColumnName, 
-      item.ichTypeText, 
-      item.batchText,
-    ]
-  })
-  return res;
-}
-</script>

+ 0 - 147
src/pages/introduction/explore.vue

@@ -1,147 +0,0 @@
-<template>
-  <CommonRoot>
-    <FlexCol :padding="30" :gap="15" backgroundColor="background.page">
-      <!-- 分类 -->
-      <CommonCategoryHome :categoryDefine="categoryDefine" />
-
-      <!-- 闽南文化概况 -->
-      <!-- <HomeTitle title="闽南文化概况(厦门市)" />
-      <SimplePageContentLoader :loader="introdData" >
-        <FlexCol>
-          <FlexRow
-            v-for="(item, i) in introdData .content.value"
-            :key="i"
-            position="relative"
-            align-items="stretch"
-          >
-            <Divider 
-              centerDot
-              type="vertical" 
-              color="primary" 
-            />
-            <Width :width="15" />
-            <Box2LineImageRightShadow
-              titleColor="title-text"
-              fixSize
-              :title="item.title"
-              :desc="item.desc"
-              :showImage="false"
-            >
-            <template #desc>
-              <TextEllipsis customContent maskColor="background.page">
-                <Parse :content="item.desc" />
-              </TextEllipsis>
-            </template>
-            </Box2LineImageRightShadow>
-          </FlexRow>
-        </FlexCol>
-      </SimplePageContentLoader>   -->
-      
-      <Footer text="我也是有底线的~" />
-    </FlexCol>
-  </CommonRoot>
-</template>
-
-<script setup lang="ts">
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
-import { CommonContentApi, GetColumListParams } from '@/api/CommonContent';
-import { useHomeCommonCategoryBlock } from '../article/common/CommonContent';
-import CommonRoot from '@/components/dialog/CommonRoot.vue';
-import FlexCol from '@/components/layout/FlexCol.vue';
-import CharacterContent from '@/api/introduction/CharacterContent';
-import CustomContent from '@/api/introduction/CustomContent';
-import FeatureContent from '@/api/introduction/FeatureContent';
-import PolicyContent from '@/api/introduction/PolicyContent';
-import HistoryContent from '@/api/introduction/HistoryContent';
-import IndexContent from '@/api/introduction/IndexContent';
-import Footer from '@/components/display/Footer.vue';
-import CommonCategoryHome, { type CategoryDefine } from '../article/common/CommonCategoryHome.vue';
-
-const categoryDefine : CategoryDefine[] = [
-  {
-    title: '文化概况',
-    content: HistoryContent,
-    type: '',
-  },
-  {
-    title: '闽南百问',
-    content: useHomeCommonCategoryBlock({
-      title: '闽南百问',
-      mainBodyColumnId: 320,
-      modelId: 18,
-      itemType: 'article-common',
-      detailsPage: '/pages/article/details',
-    }),
-    type: 'article',
-  },
-  {
-    title: '历史人物',
-    content: CharacterContent,
-    detailPage: '/pages/introduction/character/details',
-    morePage: '/pages/introduction/character/list',
-    type: '',
-  },
-  // {
-  //   title: '语言文化',
-  //   content: LanguageContent,
-  //   type: '',
-  // },
-  {
-    title: '民间习俗',
-    content: CustomContent,
-    morePage: '/pages/introduction/custom/list',
-    type: '',
-  },
-  {
-    title: '艺术特色',
-    content: FeatureContent,
-    type: '',
-  },
-  /* {
-    title: '建筑文化',
-    content: BulidingContent,
-    type: '',
-  },
-  {
-    title: '饮食文化',
-    content: VictualsContent,
-    type: '',
-  },
-  {
-    title: '海洋文化',
-    content: SeaContent,
-    type: '',
-  },*/
-  {
-    title: '政策法规',
-    content: PolicyContent,
-    type: '',
-    noFrom: true,
-    morePage: '/pages/home/laws',
-  },
-];
-const introdData = useSimpleDataLoader(async () => {
-  let i = 0;
-  const promises = [];
-  for (const item of categoryDefine) {
-    if (item.content instanceof CommonContentApi) {
-      promises.push(IndexContent.getColumList(new GetColumListParams().setSelfValues({
-        modelId: item.content.modelId,
-        mainBodyColumnId: item.content.mainBodyColumnId,
-      })))
-    }
-  }
-  const result = await Promise.all(promises);
-  return result.map((data, i) => {
-    const res = data.list[0];
-    const item = categoryDefine[i];
-    return {
-      title: item.title,
-      subtitle: '',
-      date: '',
-      desc: res.overview as string,
-      index: i,
-    }
-  });
-});
-</script>

+ 1 - 2
src/pages/introduction/news.vue

@@ -53,6 +53,7 @@
 <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';
@@ -61,13 +62,11 @@ 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 ApiCofig from '@/common/config/ApiCofig';
 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';
-import type { PickerItem } from '@/components/form/Picker';
 
 const searchText = ref('');
 const filterDate = ref(['']);

+ 0 - 144
src/pages/research/index.vue

@@ -1,144 +0,0 @@
-<template>
-  <CommonListPage 
-    itemType="article-common"
-    :dropDownNames="dropdownNames"
-    :tabs="[
-      { id: 0, text: '文化新视角' },
-      { id: 1, text: '研究机构' },
-    ]"
-    :showListTabIds="[1]"
-    :detailsPage="{
-      0: 'byContent',
-      1: '/pages/article/details',
-    }"
-    :detailsParams="detailsParams"
-    :detailsPageByContentCallback="resolveCommonContentGetPageDetailUrlAuto"
-    :startTabIndex="startTab"
-    :load="loadData" 
-  >
-    <template #list="{ tabId }">
-      <CommonCategoryHome v-if="tabId === 0" :categoryDefine="categoryDefine" />
-    </template>
-  </CommonListPage>
-</template>
-
-<script setup lang="ts">
-import { computed, ref } from 'vue';
-import { GetContentListItem, GetContentListParams } from '@/api/CommonContent';
-import CommonListPage, { type DropDownNames } from '@/pages/article/common/CommonListPage.vue';
-import ResultContent from '@/api/research/ResultContent';
-import InnovationContent from '@/api/research/InnovationContent';
-import TeamsContent from '@/api/research/TeamsContent';
-import CommonCategoryHome, { type CategoryDefine } from '../article/common/CommonCategoryHome.vue';
-import { resolveCommonContentFormData, resolveCommonContentGetPageDetailUrlAuto, resolveCommonContentMakeDoubleSpace, useHomeCommonCategoryBlock } from '../article/common/CommonContent';
-import NewsIndexContent from '@/api/news/NewsIndexContent';
-
-const dropdownNames = ref<DropDownNames[]>([]);
-const startTab = ref(0);
-async function loadData(
-  page: number, 
-  pageSize: number,
-  searchText: string,
-  dropDownValues: number[],
-  tabSelect: number,
-) {
-  let res;
-  startTab.value = tabSelect;
-  switch (tabSelect) {
-    default:
-    case 1: 
-      res = (await TeamsContent.getContentList(new GetContentListParams()
-        .setKeywords(searchText)
-      , page, pageSize))
-      break;
-  }
-  res.list.forEach((item) => {
-    item.desc = item.from ? `来源:${item.from}` : '';
-    item.bottomTags = [
-      item.levelText, 
-      item.mainBodyColumnName, 
-      item.ichTypeText, 
-      item.batchText,
-    ]
-  })
-  return res;
-}
-const detailsParams = computed(() => {
-  switch (startTab.value) {
-    case 1:
-      return {
-        modelId: TeamsContent.modelId,
-        mainBodyColumnId: TeamsContent.mainBodyColumnId,
-      };
-    default:
-    case 0:
-      return {
-        modelId: InnovationContent.modelId,
-        mainBodyColumnId: InnovationContent.mainBodyColumnId,
-      };
-  }
-});
-
-const categoryDefine : CategoryDefine[] = [
-  {
-    title: '非遗作品秀',
-    content: useHomeCommonCategoryBlock({
-      title: '非遗作品秀',
-      mainBodyColumnId: 365,
-      modelId: InnovationContent.modelId,
-      itemType: 'article-common',
-      detailsPage: 'byContent',
-      resolveData: resolveCommonContentFormData,
-    }),
-    type: '',
-  },
-  {
-    title: '非遗新青年',
-    content: useHomeCommonCategoryBlock({
-      title: '非遗新青年',
-      mainBodyColumnId: 364,
-      modelId: InnovationContent.modelId,
-      itemType: 'article-common',
-      detailsPage: 'byContent',
-      resolveData: resolveCommonContentFormData,
-    }),
-    type: '',
-  },
-  {
-    title: '非遗巴士研学',
-    content: useHomeCommonCategoryBlock({
-      title: '非遗巴士研学',
-      mainBodyColumnId: 363,
-      modelId: InnovationContent.modelId,
-      itemType: 'article-common',
-      detailsPage: 'byContent',
-      resolveData: resolveCommonContentFormData,
-    }),
-    type: '',
-  },
-  {
-    title: '"一区一节"闽南文化品牌活动',
-    content: useHomeCommonCategoryBlock({
-      title: '"一区一节"闽南文化品牌活动',
-      mainBodyColumnId: 367,
-      modelId: NewsIndexContent.modelId,
-      itemType: 'article-common',
-      detailsPage: 'byContent',
-      resolveData: resolveCommonContentFormData,
-    }),
-    type: 'large-image2',
-  },
-  {
-    title: '其他闽南文化品牌活动',
-    content: useHomeCommonCategoryBlock({
-      title: '其他闽南文化品牌活动',
-      mainBodyColumnId: 368,
-      modelId: NewsIndexContent.modelId,
-      itemType: 'article-common',
-      detailsPage: 'byContent',
-      resolveData: resolveCommonContentFormData,
-    }),
-    type: 'large-image2',
-  },
-];
-</script>

+ 3 - 7
src/pages/travel/index.vue

@@ -8,7 +8,7 @@
       align="left" 
       backgroundColor="background.page" 
     />
-    <Travel />
+    <CommonCategoryList pageConfigName="travel" />
     <Height :height="150" />
     <Tabbar :current="3" />
   </FlexCol>
@@ -20,9 +20,8 @@ import Tabbar from '@/common/components/tabs/Tabbar.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
 import NavBar from '@/components/nav/NavBar.vue';
-import Image from '@/components/basic/Image.vue';
-import Travel from '../introduction/travel.vue';
 import Height from '@/components/layout/space/Height.vue';
+import CommonCategoryList from '../article/data/CommonCategoryList.vue';
 
 onShareTimeline(() => {
   return {}; 
@@ -30,7 +29,4 @@ onShareTimeline(() => {
 onShareAppMessage(() => {
   return {}; 
 })
-</script>
-
-<style lang="scss">
-</style>
+</script>