Forráskód Böngészése

📦 一级页面ssr测试完成

imengyu 1 hónapja
szülő
commit
2d4ef19788

+ 5 - 5
nuxt.config.ts

@@ -5,14 +5,14 @@ export default defineNuxtConfig({
   srcDir: 'src/',
   modules: ['@pinia/nuxt', '@ant-design-vue/nuxt'],
   routeRules: {
-    '/': { swr: 3600 },
-    '/about': { prerender: true },
-    '/communicate': { swr: 3600 },
-    '/fusion': { swr: 3600 },
+    '/': { swr: 1800 },
+    '/about': { swr: 86400 },
+    '/communicate': { swr: 86400 },
+    '/fusion': { swr: 86400 },
     '/inheritor': { swr: 3600 },
     '/introduction': { swr: 3600 },
     '/news': { swr: 3600 },
-    '/research': { swr: 3600 },
+    '/research': { swr: 86400 },
 
     '/introduction/**': { swr: true },
     '/communicate/**': { swr: true },

+ 4 - 4
package-lock.json

@@ -9,7 +9,7 @@
       "version": "0.0.0",
       "dependencies": {
         "@ant-design-vue/nuxt": "^1.4.6",
-        "@imengyu/js-request-transform": "^0.3.3",
+        "@imengyu/js-request-transform": "^0.3.5",
         "@imengyu/vue-dynamic-form": "^0.1.1",
         "@imengyu/vue-scroll-rect": "^0.1.3",
         "@pinia/nuxt": "^0.11.1",
@@ -1062,9 +1062,9 @@
       "license": "MIT"
     },
     "node_modules/@imengyu/js-request-transform": {
-      "version": "0.3.3",
-      "resolved": "https://registry.npmmirror.com/@imengyu/js-request-transform/-/js-request-transform-0.3.3.tgz",
-      "integrity": "sha512-fIefMepOYOrZ0QT1FpJn4zQaVLNITJ+qOK+jc5PX6+nEvTWmKr5Y1CXyfILVc6oxo0N8wXYnBySvBzKdvgw8rQ==",
+      "version": "0.3.5",
+      "resolved": "https://registry.npmmirror.com/@imengyu/js-request-transform/-/js-request-transform-0.3.5.tgz",
+      "integrity": "sha512-exMBbHxYyuwP6re1lEqq4AY5R0EmvL9jYSNe1DN91GbYCvloGvBP720lJeYOIKopKRki+H84PLLWonxYMzbiDA==",
       "license": "MIT",
       "dependencies": {
         "dayjs": "^1.11.7"

+ 1 - 1
package.json

@@ -18,7 +18,7 @@
   },
   "dependencies": {
     "@ant-design-vue/nuxt": "^1.4.6",
-    "@imengyu/js-request-transform": "^0.3.3",
+    "@imengyu/js-request-transform": "^0.3.5",
     "@imengyu/vue-dynamic-form": "^0.1.1",
     "@imengyu/vue-scroll-rect": "^0.1.3",
     "@pinia/nuxt": "^0.11.1",

+ 1 - 0
src/assets/scss/news.scss

@@ -65,6 +65,7 @@
     background-color: $box-color;
     border: 1px solid $border-split-color;
     width: 100%;
+    text-decoration: none;
 
     &.row-type2 {
       flex-wrap: wrap;

+ 1 - 1
src/components/content/SimplePageContentLoader.vue

@@ -10,7 +10,7 @@
     style="min-height: 200rpx"
   >
     <a-empty :description="loader.loadError.value" >
-      <a-button  @click="handleRetry">刷新</a-button>
+      <a-button  @click="handleRetry">重试</a-button>
     </a-empty>
   </div>
   <template v-else-if="loader?.loadStatus.value == 'finished' || loader?.loadStatus.value == 'nomore'">

+ 16 - 9
src/components/controls/Pagination.vue

@@ -2,37 +2,40 @@
   <!-- 分页组件 -->
   <div class="pagination">
     <!-- 上一页按钮 -->
-    <div
+    <a
       :class="[
         'page-button',
         currentPage > 1 ? 'enable' : ''
       ]"
-      @click="goToPage(currentPage - 1)"
+      :href="ssrUrl + '?page=' + (currentPage - 1)"
+      @click.prevent="goToPage(currentPage - 1)"
     >
       &lt;
-    </div>
+    </a>
 
     <!-- 页码按钮 -->
-    <div
+    <a
       v-for="page in visiblePages"
       :key="page"
       class="page-button enable"
       :class="{ active: page === currentPage }"
-      @click="goToPage(page)"
+      :href="ssrUrl + '?page=' + page"
+      @click.prevent="goToPage(page)"
     >
       {{ page }}
-    </div>
+    </a>
 
     <!-- 下一页按钮 -->
-    <div
+    <a
       :class="[
         'page-button',
         currentPage < totalPages ? 'enable' : ''
       ]"
-      @click="goToPage(currentPage + 1)"
+      :href="ssrUrl + '?page=' + (currentPage + 1)"
+      @click.prevent="goToPage(currentPage + 1)"
     >
       &gt;
-    </div>
+    </a>
   </div>
 </template>
 
@@ -40,6 +43,10 @@
 import { ref, computed, watch } from "vue";
 
 const props = defineProps({
+  ssrUrl: {
+    type: String,
+    default: '',
+  },
   /**
    * 当前页码
    */

+ 6 - 0
src/components/display/SimpleRichHtml.vue

@@ -94,18 +94,24 @@ function generateCatalog() {
 }
 
 watch(() => props.contents, () => {
+  if (import.meta.server)
+    return;
   setTimeout(() => {
     generateCatalog();
   }, 200);
 }, { immediate: true })
 
 onBeforeUnmount(() => {
+  if (import.meta.server)
+    return;
   if (lastStyleTag) {
     lastStyleTag.parentElement?.removeChild(lastStyleTag);
     lastStyleTag = null;
   }
 })
 onMounted(() => {
+  if (import.meta.server)
+    return;
   genTagCss()
 });
 </script>

+ 1 - 1
src/components/dynamicf/NumberRange.vue

@@ -11,7 +11,7 @@
  * 下拉框表单控件,用于解决 a-select 不能选择对象的问题
  */
 import { Form, type InputNumberProps } from 'ant-design-vue';
-import { defineProps, defineEmits, type PropType, ref, watch, onMounted } from 'vue';
+import { type PropType, ref, watch, onMounted } from 'vue';
 
 const props = defineProps({
   /**

+ 1 - 1
src/components/dynamicf/RadioValue.vue

@@ -21,7 +21,7 @@
  * 下拉框表单控件,用于解决 a-select 不能选择对象的问题
  */
 import type { RadioGroupProps, RadioProps } from 'ant-design-vue';
-import { defineProps, defineEmits, type PropType, ref, watch, onMounted } from 'vue';
+import { type PropType, ref, watch, onMounted } from 'vue';
 import type { IDynamicFormItemRadioValueOption } from './RadioValue';
 
 const props = defineProps({

+ 1 - 1
src/components/dynamicf/SelectValue.vue

@@ -16,7 +16,7 @@
  * 下拉框表单控件,用于解决 a-select 不能选择对象的问题
  */
 import type { SelectProps } from 'ant-design-vue/lib/vc-select';
-import { defineProps, defineEmits, type PropType, ref, watch, onMounted } from 'vue';
+import { type PropType, ref, watch, onMounted } from 'vue';
 import type { IDynamicFormItemSelectValueOption } from './SelectValue';
 
 const props = defineProps({

+ 1 - 1
src/components/dynamicf/SimpleSelectFormItem.vue

@@ -22,7 +22,7 @@
  */
 import VNodeRenderer from '@/components/VNodeRenderer.vue';
 import type { SelectProps } from 'ant-design-vue/lib/vc-select';
-import { defineProps, defineEmits, type PropType } from 'vue';
+import { type PropType } from 'vue';
 import type { IDynamicFormItemSelectOption, RenderOption } from './SimpleSelectFormItem';
 
 defineProps({

+ 1 - 1
src/components/parts/ImageTitleBlock.vue

@@ -33,7 +33,7 @@ defineProps({
   fit: {
     type: Boolean,
     default: false
-  }
+  },
 })
 defineEmits([
   'click',

+ 28 - 6
src/components/parts/LeftRightBox.vue

@@ -6,10 +6,16 @@
           <Slide 
             v-for="(item, index) in rightItems"
             :key="index"
-            @click="() => item.onClick ? item.onClick() : emit('rightItemDefaultClick', item)"
+            @click="() => item.link ? undefined : emit('rightItemDefaultClick', item)"
           >
-            <ImageTitleBlock 
-              fit
+            <NuxtLink v-if="item.link" :to="item.link" class="w-100 h-100">
+              <ImageTitleBlock fit
+                :image="item.image"
+                :title="item.title"
+                :desc="item.desc"
+              />
+            </NuxtLink>
+            <ImageTitleBlock v-else fit
               :image="item.image"
               :title="item.title"
               :desc="item.desc"
@@ -29,6 +35,7 @@
         :descLines="descLines"
         :showExpand="showExpand"
         :more="showMore"
+        :moreLink="moreLink"
         @moreClick="emit('moreClick')"
       />
     </div>
@@ -40,6 +47,7 @@
         :descLines="descLines"
         :showExpand="showExpand"
         :more="showMore"
+        :moreLink="moreLink"
         @moreClick="emit('moreClick')"
       />
       <template v-else>
@@ -47,9 +55,16 @@
           <Slide 
             v-for="(item, index) in rightItems"
             :key="index"
-            @click="() => item.onClick ? item.onClick() : emit('rightItemDefaultClick', item)"
+            @click="() => item.link ? undefined : emit('rightItemDefaultClick', item)"
           >
-            <ImageTitleBlock fit
+            <NuxtLink v-if="item.link" :to="item.link" class="w-100 h-100">
+              <ImageTitleBlock fit
+                :image="item.image"
+                :title="item.title"
+                :desc="item.desc"
+              />
+            </NuxtLink>
+            <ImageTitleBlock v-else fit
               :image="item.image"
               :title="item.title"
               :desc="item.desc"
@@ -60,6 +75,9 @@
             <Pagination />
           </template> 
         </Carousel>
+        <NuxtLink v-else-if="moreLink" :to="moreLink">
+          <img :src="image" alt="image" class="h-100" />
+        </NuxtLink>
         <img v-else :src="image" alt="image" class="h-100" @click="emit('moreClick')" />
       </template>
     </div>
@@ -96,7 +114,7 @@ defineProps({
       title: string, 
       desc: string, 
       image: string ,
-      onClick?: () => void,
+      link?: string,
     }>|null>,
     default: null,
   },
@@ -116,6 +134,10 @@ defineProps({
     type: Boolean,
     default: true,
   },
+  moreLink: {
+    type: String,
+    default: '',
+  },
 })
 
 const emit = defineEmits([	

+ 14 - 10
src/components/parts/ThreeImageList.vue

@@ -1,13 +1,17 @@
 <template>
   <div class="ThreeImageList d-flex flex-row flex-wrap th">
-    <ImageTitleBlock 
+    <NuxtLink 
       v-for="(item, index) in list"
       :key="index"
-      :image="item.image"
-      :title="item.title"
-      :desc="item.desc"
-      @click="item.onClick?.()"
-    />
+      :to="item.link"
+      class="item"
+    >
+      <ImageTitleBlock 
+        :image="item.image"
+        :title="item.title"
+        :desc="item.desc"
+      />
+    </NuxtLink>
   </div>
 </template>
 
@@ -17,7 +21,7 @@ import ImageTitleBlock from './ImageTitleBlock.vue';
 
 defineProps({	
   list : {
-    type: Object as PropType<{title: string, image: string, desc: string,onClick?:() => void}[]>,
+    type: Object as PropType<{title: string, image: string, desc: string, link: string }[]>,
     default: () => [],
   }	
 })
@@ -28,7 +32,7 @@ defineProps({
   position: relative;
   gap: 30px;
 
-  .ImageTitleBlock {
+  .item {
     width: calc(33.33% - 20px);
     margin-right: 0;
   }
@@ -36,14 +40,14 @@ defineProps({
 
 @media (max-width: 900px) {
   .ThreeImageList {
-    .ImageTitleBlock {
+    .item {
       width: calc(50% - 15px);
     }
   }
 }
 @media (max-width: 600px) {
   .ThreeImageList {
-    .ImageTitleBlock {
+    .item {
       width: 100%;
     }
   }

+ 15 - 7
src/components/parts/TitleDescBlock.vue

@@ -2,7 +2,7 @@
   <div class="TitleDescBlock">
     <h3>{{ title }}</h3>
     <span v-if="date" class="time">{{ date }}</span>
-    <p :class="expand?'expand':'no-expand'" v-html="desc"></p>
+    <SimpleRichHtml hydrate-never :class="'desc ' + (expand?'expand':'no-expand')" :contents="[desc]" />
     <slot name="addon" />
 
     <div class="footer">
@@ -10,9 +10,11 @@
         {{expand?'折叠':'展开'}}
         <img src="@/assets/images/IconArrowRight.png" />
       </div>
-      <div v-if="more" class="more" @click="emit('moreClick')">
-        更多
-        <img src="@/assets/images/IconArrowRight.png" alt="更多" />
+      <div v-if="more" class="more" @click="moreLink ? undefined : emit('moreClick')">
+        <NuxtLink :to="moreLink">
+          更多
+          <img src="@/assets/images/IconArrowRight.png" alt="更多" />
+        </NuxtLink>
       </div>
     </div>
   </div>
@@ -20,6 +22,7 @@
 
 <script setup lang="ts">
 import { ref } from 'vue';
+import SimpleRichHtml from '../display/SimpleRichHtml.vue';
 
 const props = defineProps({	
   title : {
@@ -38,6 +41,10 @@ const props = defineProps({
     type: Boolean,
     default: false,
   },
+  moreLink: {
+    type: String,
+    default: '',
+  },
   showExpand: {
     type: Boolean,
     default: false,
@@ -52,7 +59,8 @@ const expand = ref(false)
 
 const emit = defineEmits([	
   "moreClick"	
-])
+]);
+
 </script>
 
 <style lang="scss">
@@ -70,7 +78,7 @@ const emit = defineEmits([
     margin-bottom: 8px;
   }
 
-  p,
+  .desc,
   .time {
     color: $text-content-second-color;
     font-size: 0.85rem;
@@ -79,7 +87,7 @@ const emit = defineEmits([
   .time {
     margin-bottom: 16px;
   }
-  > p {
+  > .desc {
     display: -webkit-box;
     -webkit-box-orient: vertical;
     margin: 0;

+ 55 - 0
src/composeable/SimpleDataLoader.ts

@@ -54,4 +54,59 @@ export function useSimpleDataLoader<T, P = any>(
     loadData,
     getLastParams: () => lastParams,
   }
+}
+
+export async function useSSrSimpleDataLoader<T, P = any>(
+  name: string,
+  loader: (params?: P) => Promise<T>,
+  params : P|undefined = undefined,
+  emptyIfArrayEmpty = true,
+)  : Promise<ISimpleDataLoader<T, P>>
+ {
+  const route = useRoute();
+
+  let lastParams: P | undefined = params;
+  const loadStatus = ref<LoaderLoadType>('finished');
+  const loadError = ref('');
+  const { data: content, error } = (await useAsyncData(route.fullPath + '/' + name, () => loader(lastParams)))
+
+
+  async function loadData(params?: P) {
+    /* if (!import.meta.client)
+      return;
+    if (params)
+      lastParams = params;
+    loadStatus.value = 'loading';
+    try {
+      const res = await loader(params ?? lastParams) as T;
+      console.log('res', res);
+      
+      content.value = res as any;
+      if (Array.isArray(res) && emptyIfArrayEmpty && (res as any[]).length === 0)
+        loadStatus.value = 'nomore';
+      else
+        loadStatus.value = 'finished';
+      loadError.value = '';
+    } catch(e) {
+      loadError.value = '' + e;
+      loadStatus.value = 'error';
+      console.log(e);
+      
+    } */
+  }
+
+  watch(error, (e) => {
+    if (e) {
+      loadError.value = '' + e;
+      loadStatus.value = 'error';
+    }
+  }, { immediate: true });
+  
+  return {
+    content: content as Ref<T|null>,
+    loadStatus,
+    loadError,
+    loadData,
+    getLastParams: () => lastParams,
+  }
 }

+ 105 - 0
src/composeable/SimplePagerDataLoader.ts

@@ -1,5 +1,6 @@
 import { watch, ref, computed, type Ref } from "vue"
 import type { ILoaderCommon, LoaderLoadType } from "./LoaderCommon";
+import { formatError } from "~/components/error/ErrorReporterIs";
 
 export interface ISimplePageListLoader<T, P> extends ILoaderCommon<P> {
   list: Ref<T[]>;
@@ -125,4 +126,108 @@ export function useSimplePagerDataLoader<T, P = any>(
     loadError,
     loadStatus,
   }
+}
+
+export async function useSSrSimplePagerDataLoader<T, P = any>(
+  name: string,
+  startPage: number,
+  pageSize: number|Ref<number>, 
+  loader: (page: number, pageSize: number, params?: P) => Promise<{
+    data: T[],
+    total: number,
+  }>,
+  params : P|undefined = undefined,
+) : Promise<ISimplePageListLoader<T, P>>
+{
+  const route = useRoute();
+
+  let lastParams: P | undefined = params;
+
+  const page = ref(startPage);
+  const { 
+    data, 
+    error 
+  } = (await useAsyncData(route.fullPath + '/' + name, () => loader(page.value, getPageSize(), lastParams)))
+
+  const list = ref<T[]>([]) as Ref<T[]>;
+  const total = ref(0);
+  const totalPages = computed(() => Math.ceil(total.value / getPageSize()));
+  const loadStatus = ref<LoaderLoadType>('finished');
+  const loadError = ref('');
+
+  if (error.value) {
+    loadError.value = '' + formatError (error.value);
+    loadStatus.value = 'error';
+  } else if (data.value) {
+    list.value = data.value.data as any;
+    total.value = data.value.total as any;
+    loadError.value = '';
+    loadStatus.value = 'finished';
+  }
+
+  function getPageSize() {
+    return typeof pageSize == 'object'? pageSize.value : pageSize;
+  }
+
+  watch(page, async () => {
+    await loadData(lastParams, false);
+  });
+
+  let loading = false;
+
+  async function loadData(params?: P, refresh: boolean = false) {
+    if (loading) 
+      return;
+    if (params)
+      lastParams = params;
+    if (refresh) {
+      page.value = 1;
+    }
+    list.value = []; 
+    loadStatus.value = 'loading';
+    loading = true;
+
+    try {
+      const res = (await loader(page.value, getPageSize(), lastParams));
+      list.value = list.value.concat(res.data);
+      total.value = res.total;
+      loadStatus.value = list.value.length > 0 ? 'finished' : 'nomore';
+      loadError.value = '';
+      loading = false;
+    } catch(e) {
+      loadError.value = '' + formatError(e);
+      loadStatus.value = 'error';
+      loading = false;
+    }
+  }
+  /**
+   * 下一页
+   */
+  async function next() {
+    if (page.value > total.value)
+      return;
+    page.value++;
+    await loadData(lastParams, false);
+  }
+  /**
+   * 上一页
+   */
+  async function prev() {
+    if (page.value <= 1)
+      return;   
+    page.value--;
+    await loadData(lastParams, false);
+  }
+
+  return {
+    loadData,
+    next,
+    prev,
+    list: list as any as Ref<T[]>,
+    page,
+    total,
+    totalPages,
+    loadError,
+    loadStatus,
+  }
 }

+ 39 - 30
src/pages/about.vue

@@ -12,7 +12,6 @@
       </template>
     </Carousel>
 
-
     <!-- 头部搜索 -->
     <section class="main-section absolute light fit-small-header">
       <div class="content row">
@@ -51,8 +50,7 @@
             :desc="introdLoader.content.value?.introd1?.content"
             :image="introdLoader.content.value?.introd1?.image"
             :rightItems="introdLoader.content.value?.list"
-            @rightItemDefaultClick="(item) => navTo('/news/detail', { id: item.id })"
-            @moreClick="navTo('/introduction/about')"
+            :moreLink="router.resolve({ path: '/introduction/about' }).href"
           />
           <div class="d-flex justify-content-center mt-5">
             <h2>{{ introdLoader.content.value?.introd2?.title }}</h2>
@@ -62,7 +60,7 @@
             :title="introdLoader.content.value?.introd2?.title"
             :desc="introdLoader.content.value?.introd2?.content"
             :image="introdLoader.content.value?.introd2?.image"
-            @moreClick="navTo('/introduction/about')"
+            :moreLink="router.resolve({ path: '/introduction/about' }).href"
           />
           <div class="d-flex justify-content-center mt-5">
             <h2>{{ introdLoader.content.value?.introd3?.title }}</h2>
@@ -84,23 +82,24 @@
       <div class="content news-list">
         <!-- 新闻列表 -->
         <SimplePageContentLoader :loader="newsLoader">
-          <div 
+          <NuxtLink 
             v-for="(item, k) in newsLoader.list.value"
             :key="item.id"
             class="item"
-            @click="router.push({ path: 'news/detail', query: { id: item.id }})"
+            :to="{ path: 'news/detail', query: { id: item.id }}"
           >
             <img :src="item.image" alt="新闻图片" />
             <TitleDescBlock
               :title="item.title"
               :desc="item.desc || item.title"
             />
-          </div>
+          </NuxtLink>
         </SimplePageContentLoader>
         <!-- 分页 -->
         <Pagination2
           v-model:currentPage="newsLoader.page.value"
           :totalPages="newsLoader.totalPages.value"
+          :ssrUrl="router.resolve(route).href"
         />
       </div>
     </section>
@@ -110,20 +109,25 @@
       <div class="content">
         <div class="title left-right">
           <h2>政策法规</h2>
-          <div class="small-more" @click="navTo('/introduction/policy')">
-            <span>更多信息</span>
-            <img src="@/assets/images/index/ButtonMore.png" alt="更多" />
+          <div class="small-more">
+            <NuxtLink :to="{ path: '/introduction/policy' }">
+              <span>更多信息</span>
+              <img src="@/assets/images/index/ButtonMore.png" alt="更多" />
+            </NuxtLink>
           </div>
         </div>
         <SimplePageContentLoader :loader="lawsData">
-          <ImageTextSmallBlock
+          <NuxtLink 
             v-for="(item, index) in lawsData.content.value"
             :key="index"
-            :title="item.title"
-            :image="item.image"
-            :date="item.date"
-            @click="navTo('/news/detail', { id: item.id })"
-          />
+            :to="{ path: '/news/detail', query: { id: item.id } }"
+          >
+            <ImageTextSmallBlock
+              :title="item.title"
+              :image="item.image"
+              :date="item.date"
+            />
+          </NuxtLink>
         </SimplePageContentLoader>
       </div>
     </section>
@@ -133,10 +137,9 @@
 <script setup lang="ts">
 import { Carousel, Slide, Pagination, Navigation } from 'vue3-carousel'
 import { onMounted, ref, watch } from 'vue';
-import { useSimplePagerDataLoader } from '@/composeable/SimplePagerDataLoader';
+import { useSimplePagerDataLoader, useSSrSimplePagerDataLoader } from '@/composeable/SimplePagerDataLoader';
 import { useRouter } from 'vue-router';
-import { useSimpleDataLoader } from '@/composeable/SimpleDataLoader';
-import { usePageAction } from '@/composeable/PageAction';
+import { useSSrSimpleDataLoader } from '@/composeable/SimpleDataLoader';
 import Pagination2 from '@/components/controls/Pagination.vue';
 import TitleDescBlock from '@/components/parts/TitleDescBlock.vue';
 import ImageTextSmallBlock from '@/components/parts/ImageTextSmallBlock.vue';
@@ -148,7 +151,8 @@ import LawsTest from '@/assets/images/inheritor/LawsTest.jpg'
 import LeftRightBox from '@/components/parts/LeftRightBox.vue';
 import NewsIndexContent from '@/api/news/NewsIndexContent';
 
-const { navTo } = usePageAction();
+const router = useRouter();
+const route = useRoute();
 
 const carouselConfig = {
   itemsToShow: 1,
@@ -163,13 +167,11 @@ const mainTabs = [
 ]
 const mainTabActive = ref(0);
 
-const router = useRouter();
-
 function handleTabClick(value: number) {
   mainTabActive.value = value; 
 }
 
-const introdLoader = useSimpleDataLoader(async () => {
+const introdLoader = await useSSrSimpleDataLoader('introd', async () => {
   const res = await CommonContent.getContentList(new GetContentListParams()
         .setModelId(17)
         .setMainBodyColumnId([ 256, 283, 284 ])
@@ -183,17 +185,23 @@ const introdLoader = useSimpleDataLoader(async () => {
   const id2 = res2.list.find(item => item.title.includes('闽南') && !item.title.includes('厦门'))?.id; 
   const id3 = res2.list.find(item => item.title.includes('全国'))?.id; 
   
-  const introd1 = id1 ? await NewsIndexContent.getContentDetail(id1) : undefined;
-  const introd2 = id2 ? await NewsIndexContent.getContentDetail(id2) : undefined;
-  const introd3 = id3 ? await NewsIndexContent.getContentDetail(id3) : undefined;
+  const introd1 = id1 ? (await NewsIndexContent.getContentDetail(id1)).toJSON() : undefined;
+  const introd2 = id2 ? (await NewsIndexContent.getContentDetail(id2)).toJSON() : undefined;
+  const introd3 = id3 ? (await NewsIndexContent.getContentDetail(id3)).toJSON() : undefined;
   return {
     introd1,
     introd2,
     introd3,
-    list: res.list,
+    list: res.list.map(p => ({
+      id: p.id,
+      title: p.title,
+      desc: p.desc,
+      image: p.image,
+      link: router.resolve({ path: '/news/detail', query: { id: p.id } }).href,
+    })),
   };
 })
-const newsLoader = useSimplePagerDataLoader(10, async (page, pageSize) => {
+const newsLoader = await useSSrSimplePagerDataLoader('news', Number(route.query.page as string || 1), 10, async (page, pageSize) => {
   let res;
   switch (mainTabActive.value) {
     default:
@@ -205,17 +213,18 @@ const newsLoader = useSimplePagerDataLoader(10, async (page, pageSize) => {
       break;
   }
   return {
-    data: res.list,
+    data: res.list.map(p => p.toJSON()),
     total: res.total,
   };
 });
-const lawsData = useSimpleDataLoader(async () => 
+const lawsData = await useSSrSimpleDataLoader('laws', async () => 
   (await PolicyContent.getContentList(new GetContentListParams(), 1, 8))
     .list?.map(item => ({
       id: item.id,
       title: item.title,
       image: item.image || LawsTest,
       date: DateUtils.formatDate(item.publishAt, DateUtils.FormatStrings.YearCommonShort),
+      link: router.resolve({ path: '/news/detail', query: { id: item.id } }).href,
     }))
 )
 

+ 9 - 11
src/pages/communicate.vue

@@ -23,7 +23,7 @@
           title="闽南地区文化交流"
           :desc="overviewsLoader.content.value?.[0]"
           :image="Image1"
-          @moreClick="navTo('/communicate/fujian-and-taiwan')"
+          :moreLink="router.resolve('/communicate/fujian-and-taiwan').href"
         />
         <LeftRightBox 
           class="mt-4"
@@ -31,14 +31,14 @@
           :desc="overviewsLoader.content.value?.[1]"
           :image="Image2"
           left
-          @moreClick="navTo('/communicate/hk-macao-and-taiwan')"
+          :moreLink="router.resolve('/communicate/hk-macao-and-taiwan').href"
         />
         <LeftRightBox 
           class="mt-4"
           title="对外文化交流"
           :desc="overviewsLoader.content.value?.[2]"
           :image="Image3"
-          @moreClick="navTo('/communicate/outside')"
+          :moreLink="router.resolve('/communicate/outside').href"
         />
       
       </div>
@@ -50,16 +50,16 @@
 
 <script setup lang="ts">
 import { Carousel, Slide, Pagination, Navigation } from 'vue3-carousel'
-import { onMounted, ref } from 'vue';
-import { usePageAction } from '@/composeable/PageAction';
-import { useSimpleDataLoader } from '@/composeable/SimpleDataLoader';
+import { useSSrSimpleDataLoader } from '@/composeable/SimpleDataLoader';
 import Image1 from '@/assets/images/communicate/Image1.jpg'
 import Image2 from '@/assets/images/communicate/Image2.jpg'
 import Image3 from '@/assets/images/communicate/Image3.jpg'
 import LeftRightBox from '@/components/parts/LeftRightBox.vue';
-import CommonContent, { GetColumListParams } from '@/api/CommonContent';
-import CommunicateContent from '@/api/communicate/CommunicateContent';
+import { GetColumListParams } from '@/api/CommonContent';
 import { NO_CONTENT_STRING } from '@/common/ConstStrings';
+import CommunicateContent from '@/api/communicate/CommunicateContent';
+
+const router = useRouter();
 
 const carouselConfig = {
   itemsToShow: 1,
@@ -67,9 +67,7 @@ const carouselConfig = {
   autoPlay: 5000,
 }
 
-const { navTo } = usePageAction();
-
-const overviewsLoader = useSimpleDataLoader(async () => {
+const overviewsLoader = await useSSrSimpleDataLoader('overviews', async () => {
   return [
     (await CommunicateContent.getColumList(
       new GetColumListParams().setMainBodyColumnId(260)

+ 83 - 96
src/pages/fusion.vue

@@ -22,14 +22,18 @@
           <div class="col col-12 col-lg-6 col-xl-6 p-0">
             <ScrollRect class="left-list">
               <h3 class="month-title">{{monthSelected}}月</h3>
-              <ImageTitleDescBlock
+              <NuxtLink 
                 v-for="(item, index) in daysData"
                 :key="index"
-                :title="item.title"
-                :image="item.image"
-                :desc="item.desc || item.title"
-                @click="goDetail(item.id)"
-              />
+                :to="{ path: `/news/detail`, query: { id: item.id, modelId: 18 } }"
+                class="link-wrapper"
+              >
+                <ImageTitleDescBlock
+                  :title="item.title"
+                  :image="item.image"
+                  :desc="item.desc || item.title"
+                />
+              </NuxtLink>
             </ScrollRect>
           </div>
           <div class="month-grid col-12 col-lg-6 col-xl-6 p-0">
@@ -64,8 +68,6 @@
         <ThreeImageList :list="list" />
       </div>
     </section>
-
-
   </div>
 </template>
 
@@ -78,14 +80,13 @@ import Image3 from '@/assets/images/inheritor/Image3.jpg'
 import Image4 from '@/assets/images/fusion/Image4.jpg'
 import Image5 from '@/assets/images/fusion/Image5.jpg'
 import Image6 from '@/assets/images/fusion/Image6.jpg'
-import LeftRightBox from '@/components/parts/LeftRightBox.vue';
 import ImageTitleDescBlock from '@/components/parts/ImageTitleDescBlock.vue';
 import ThreeImageList from '@/components/parts/ThreeImageList.vue';
-import { usePageAction } from '@/composeable/PageAction';
-import CommonContent, { GetContentListItem, GetContentListParams } from '@/api/CommonContent';
 import CalendarContent from '@/api/fusion/CalendarContent';
+import { GetContentListItem, GetContentListParams } from '@/api/CommonContent';
 import { ScrollRect } from '@imengyu/vue-scroll-rect';
 
+const router = useRouter();
 const carouselConfig = {
   itemsToShow: 1,
   wrapAround: true,
@@ -96,111 +97,99 @@ const list = [
     title: '闽南文化景区',
     desc: '让文化因传承而永存',
     image: Image1,
-    onClick: () => {
-      navTo('/fusion/scenic-spot');
-    }
+    link: router.resolve('/fusion/scenic-spot').href,
   },
   {
     title: '文化旅游路线',
     desc: '让文化因传承而永存',
     image: Image2,
-    onClick: () => {
-      navTo('/fusion/route');
-    }
+    link: router.resolve('/fusion/route').href,
   },
   {
     title: '非遗研学点',
     desc: '',
     image: Image6,
-    onClick: () => {
-      navTo('/fusion/point');
-    }
+    link: router.resolve('/fusion/point').href,
   },
   {
     title: '非遗作品',
     desc: '让文化因传承而永存',
     image: Image3,
-    onClick: () => {
-      navTo('/inheritor/products');
-    }
+    link: router.resolve('/inheritor/products').href,
   },
   {
     title: '融合发展',
     desc: '非遗与旅游融合发展推荐目录',
     image: Image4,
-    onClick: () => {
-      navTo('/fusion/demo-site');
-    }
+    link: router.resolve('/fusion/demo-site').href,
   },
   {
     title: '闽南时尚',
     desc: '让文化因传承而永存',
     image: Image5,
-    onClick: () => {
-      navTo('/fusion/fashion');
-    }
+    link: router.resolve('/fusion/fashion').href,
   },
 ]
 const daysData = ref<GetContentListItem[]>([]) as Ref<GetContentListItem[]>
 const monthSelected = ref(new Date().getMonth() + 1)
-const monthData = ref<{
-  month: number;
-  holidays: GetContentListItem[];
-}[]>([
-  {
-    month: 1,
-    holidays: []
-  },
-  {
-    month: 2,
-    holidays: []
-  },
-  {
-    month: 3,
-    holidays: []
-  },
-  {
-    month: 4,
-    holidays: []
-  },
-  {
-    month: 5,
-    holidays: []
-  },
-  {
-    month: 6,
-    holidays: []
-  },
-  {
-    month: 7,
-    holidays: []
-  },
-  {
-    month: 8,
-    holidays: []
-  },
-  {
-    month: 9,
-    holidays: []
-  },
-  {
-    month: 10,
-    holidays: []
-  },
-  {
-    month: 11,
-    holidays: []
-  },
-  {
-    month: 12,
-    holidays: []
-  },
-])
-
-onMounted(async () => {
+const { data: monthData } = await useAsyncData('funCalendarContent2', async () => {
   const res = await CalendarContent.getCalendarList(new GetContentListParams(), 1, 50)
   const year = new Date().getFullYear();
 
+  const monthData : {
+    month: number;
+    holidays: GetContentListItem[];
+  }[] = [
+    {
+      month: 1,
+      holidays: []
+    },
+    {
+      month: 2,
+      holidays: []
+    },
+    {
+      month: 3,
+      holidays: []
+    },
+    {
+      month: 4,
+      holidays: []
+    },
+    {
+      month: 5,
+      holidays: []
+    },
+    {
+      month: 6,
+      holidays: []
+    },
+    {
+      month: 7,
+      holidays: []
+    },
+    {
+      month: 8,
+      holidays: []
+    },
+    {
+      month: 9,
+      holidays: []
+    },
+    {
+      month: 10,
+      holidays: []
+    },
+    {
+      month: 11,
+      holidays: []
+    },
+    {
+      month: 12,
+      holidays: []
+    },
+  ];
+
   res.list.filter(p => 
     p.dateYear === year
   ).forEach(item => {
@@ -210,23 +199,16 @@ onMounted(async () => {
   });
 
   res.list.forEach(item => {
-    monthData.value[item.dateMonth - 1]?.holidays?.push(item)
+    monthData[item.dateMonth - 1]?.holidays?.push(item.toJSON() as any)
   })
+  daysData.value = monthData[ monthSelected.value - 1].holidays as GetContentListItem[];
+  return monthData;
+})
 
-  daysData.value = monthData.value[ monthSelected.value - 1].holidays as GetContentListItem[];
-  
-});
-
-function goDetail(id: number) {
-  navTo(`/news/detail`, { id, modelId: 18 });
-}
 function monthChange(month: number) {
   monthSelected.value = month;
-  daysData.value = monthData.value[month - 1].holidays as GetContentListItem[];
+  daysData.value = monthData.value?.[month - 1]?.holidays as GetContentListItem[] || [];
 }
-
-const { navTo } = usePageAction();
-
 </script>
 
 <style lang="scss">
@@ -239,6 +221,11 @@ const { navTo } = usePageAction();
     height: 750px;
   }
 
+  .link-wrapper {
+    text-decoration: none;
+    color: $text-color-light;
+  }
+
   .month-title {
     margin: 24px 24px 0 24px;
   }

+ 72 - 63
src/pages/index.vue

@@ -41,14 +41,15 @@
                 <div class="content">
                   <h4>{{ stat.title || '\u200b' }}</h4>
                   <div class="descs">
-                    <div 
+                    <a 
                       v-for="(data, key2) in stat.datas"
+                      class="box"
                       :key="key2"
-                      @click="data.onClick"
+                      :href="data.link"
                     >
                       <h5>{{ data.title }}</h5>
                       <p>{{ data.value }}</p>
-                    </div>
+                    </a>
                   </div>
                 </div>
               </div>
@@ -97,14 +98,17 @@
 
         <SimplePageContentLoader :loader="recommend2Data">
           <div class="main-grid9 d-flex flex-row flex-wrap justify-content-between">
-            <ImageTitleBlock 
+            <NuxtLink 
               v-for="(item, index) in recommend2Data.content.value"
               :key="index"
-              :image="item.image"
-              :title="item.title"
-              :desc="item.desc"
-              @click="router.push({ path: '/details/intangible', query: { id: item.id } })"
-            />
+              :to="{ path: '/details/intangible', query: { id: item.id } }"
+            >
+              <ImageTitleBlock 
+                :image="item.image"
+                :title="item.title"
+                :desc="item.desc"
+              />
+            </NuxtLink>
           </div>
         </SimplePageContentLoader>
       </div>
@@ -119,14 +123,17 @@
 
         <SimplePageContentLoader :loader="recommend1Data">
           <div class="main-grid9 d-flex flex-row flex-wrap justify-content-between">
-            <ImageTitleBlock 
+            <NuxtLink 
               v-for="(item, index) in recommend1Data.content.value"
               :key="index"
-              :image="item.image"
-              :title="item.title"
-              :desc="item.desc"
-              @click="router.push({ path: '/details/artifact', query: { id: item.id } })"
-            />
+              :to="{ path: '/details/artifact', query: { id: item.id } }"
+            >
+              <ImageTitleBlock 
+                :image="item.image"
+                :title="item.title"
+                :desc="item.desc"
+              />
+            </NuxtLink>
           </div>
         </SimplePageContentLoader>
       </div>
@@ -144,13 +151,14 @@
             <Slide 
               v-for="(item, index) in recommend3Data.content.value"
               :key="index"
-              @click="router.push({ path: '/news/detail', query: { id: item.id } })"
             >
-              <ImageTitleBlock 
-                :image="item.image"
-                :title="item.title"
-                :desc="item.desc"
-              />
+              <NuxtLink :to="{ path: '/news/detail', query: { id: item.id } }">
+                <ImageTitleBlock 
+                  :image="item.image"
+                  :title="item.title"
+                  :desc="item.desc"
+                />
+              </NuxtLink>
             </Slide>
           </Carousel>
         </SimplePageContentLoader>
@@ -167,22 +175,27 @@
       <div class="content">
         <div class="title left-right">
           <h2>最新资讯动态</h2>
-          <div class="small-more" @click="router.push({ path: '/news' })">
-            <span>更多动态信息</span>
-            <img src="@/assets/images/index/ButtonMore.png" alt="更多" />
+          <div class="small-more">
+            <NuxtLink :to="{ path: '/news' }">
+              <span>更多动态信息</span>
+              <img src="@/assets/images/index/ButtonMore.png" alt="更多" />
+            </NuxtLink>
           </div>
         </div>
 
         <SimplePageContentLoader :loader="newsData">
           <div class="main-grid9 d-flex flex-row flex-wrap justify-content-between">
-            <ImageTitleBlock
+            <NuxtLink 
               v-for="(item, index) in newsData.content.value"
               :key="index"
-              :image="item.image"
-              :title="item.title"
-              :desc="item.typeText"
-              @click="router.push({ path: '/news/detail', query: { id: item.id } })"
-            />
+              :to="{ path: '/news/detail', query: { id: item.id } }"
+            >
+              <ImageTitleBlock
+                :image="item.image"
+                :title="item.title"
+                :desc="item.typeText"
+              />
+            </NuxtLink>
           </div>
         </SimplePageContentLoader>
       </div>
@@ -211,25 +224,25 @@
 
 <script setup lang="ts">
 import { Carousel, Slide, Pagination, Navigation } from 'vue3-carousel'
-import { onMounted, ref } from 'vue';
+import { ref } from 'vue';
 import { useRouter } from 'vue-router';
-import { useSimpleDataLoader } from '@/composeable/SimpleDataLoader';
-import CommonContent, { GetColumListParams, GetContentListParams, type GetContentListItem } from '@/api/CommonContent';
+import { useSSrSimpleDataLoader } from '@/composeable/SimpleDataLoader';
 import { NO_CONTENT_STRING } from '@/common/ConstStrings';
-import NewsIndexContent from '@/api/news/NewsIndexContent';
+import { DataDateUtils } from '@imengyu/js-request-transform';
+import { ScrollRect } from '@imengyu/vue-scroll-rect';
 import ImageTitleBlock from '@/components/parts/ImageTitleBlock.vue';
+import SimpleRemoveRichHtml from '@/components/display/SimpleRemoveRichHtml.vue';
 import SimplePageContentLoader from '@/components/content/SimplePageContentLoader.vue';
 import IndexContent from '@/api/introduction/IndexContent';
-import SimpleRemoveRichHtml from '@/components/display/SimpleRemoveRichHtml.vue';
+import NewsIndexContent from '@/api/news/NewsIndexContent';
 import UnmoveableContent from '@/api/inheritor/UnmoveableContent';
 import ProjectContent from '@/api/research/ProjectContent';
 import ActivityContent from '@/api/inheritor/ActivityContent';
 import ProductContent from '@/api/fusion/ProductContent';
 import ProductsContent from '@/api/inheritor/ProductsContent';
 import ProjectsContent from '@/api/inheritor/ProjectsContent';
-import { DataDateUtils } from '@imengyu/js-request-transform';
-import { ScrollRect } from '@imengyu/vue-scroll-rect';
 import SeminarContent from '@/api/inheritor/SeminarContent';
+import CommonContent, { GetColumListParams, GetContentListParams, type GetContentListItem } from '@/api/CommonContent';
 
 const router = useRouter();
 
@@ -245,35 +258,33 @@ const carousel2Config = {
 }
 const carousel6Ref = ref<any>(null);
 
-
-
-const bannerData = useSimpleDataLoader(async () => {
-  return await IndexContent.getBanner()
+const bannerData = await useSSrSimpleDataLoader('banner', async () => {
+  return (await IndexContent.getBanner()).map(p => p.toJSON());
 });
-const overviewData = useSimpleDataLoader(async () => {
+const overviewData = await useSSrSimpleDataLoader('overview', async () => {
   return (await IndexContent.getColumList(new GetColumListParams().setSelfValues({
     modelId: 17,
     mainBodyColumnId: 234,
   }))).list[0]?.overview || NO_CONTENT_STRING
 });
-const recommend1Data = useSimpleDataLoader<GetContentListItem[]>(async () => {
-  return (await UnmoveableContent.getContentList(new GetContentListParams(), 1, 9)).list;
+const recommend1Data = await useSSrSimpleDataLoader('recommend1', async () => {
+  return (await UnmoveableContent.getContentList(new GetContentListParams(), 1, 9)).list.map(p => p.toJSON());
 });
-const recommend2Data = useSimpleDataLoader<GetContentListItem[]>(async () => {
-  return (await ProjectsContent.getContentList(new GetContentListParams().setMainBodyColumnId([]), 1, 9)).list
+const recommend2Data = await useSSrSimpleDataLoader('recommend2', async () => {
+  return (await ProjectsContent.getContentList(new GetContentListParams().setMainBodyColumnId([]), 1, 9)).list.map(p => p.toJSON());
 });
-const recommend3Data = useSimpleDataLoader<GetContentListItem[]>(async () => {
+const recommend3Data = await useSSrSimpleDataLoader('recommend3', async () => {
   return (await CommonContent.getContentList(new GetContentListParams()
     .setModelId(17)
     .setMainBodyColumnId(273)
-  , 1, 8)).list
+  , 1, 8)).list.map(p => p.toJSON());
 });
-const newsData = useSimpleDataLoader<GetContentListItem[]>(async () => {
+const newsData = await useSSrSimpleDataLoader('news', async () => {
   return (await NewsIndexContent.getContentList(new GetContentListParams()
     .setMainBodyColumnId([ 228/* , 298, 299 */ ])
-  , 1, 9)).list
+  , 1, 9)).list.map(p => p.toJSON());
 });
-const statsData = useSimpleDataLoader(async () => {
+const statsData = await useSSrSimpleDataLoader('stats', async () => {
   const data = (await IndexContent.getStats());
   const semiCount = (await SeminarContent.getContentList(new GetContentListParams(), 1, 1)).total;
   const unmoveableCount = (await UnmoveableContent.getContentList(new GetContentListParams(), 1, 1)).total;
@@ -286,9 +297,7 @@ const statsData = useSimpleDataLoader(async () => {
         return {
           title: item.level_text,
           value: item.total,
-          onClick: () => {
-            router.push({ path: '/inheritor/projects', query: { level: item.level } });
-          },
+          link: router.resolve({ path: '/inheritor/projects', query: { level: item.level } }).href,
         }
       })
     },
@@ -299,9 +308,7 @@ const statsData = useSimpleDataLoader(async () => {
         return {
           title: item.title,
           value: item.total,
-          onClick: () => {
-            router.push({ path: '/inheritor/inheritor', query: { level: item.level } });
-          },
+          link: router.resolve({ path: '/inheritor/inheritor', query: { level: item.level } }).href
         }
       })
     },
@@ -312,17 +319,17 @@ const statsData = useSimpleDataLoader(async () => {
         {
           title: '传习所',
           value: semiCount,
-          onClick: () => router.push({ path: '/inheritor/seminar' }),
+          link: router.resolve({ path: '/inheritor/seminar' }).href,
         },
         {
           title: '传统村落',
           value: data.villageData[0]?.total ?? 0,
-          onClick: () => router.push({ path: '/village/index' }),
+          link: router.resolve({ path: '/village/index' }).href,
         },
         {
           title: '文物古迹',
           value: unmoveableCount,
-          onClick: () => router.push({ path: '/inheritor/unmoveable' }),
+          link: router.resolve({ path: '/inheritor/unmoveable' }).href,
         },
       ],
     },
@@ -368,14 +375,14 @@ const statsData = useSimpleDataLoader(async () => {
     },*/
   ];
 });
-const recordData = useSimpleDataLoader<GetContentListItem[]>(async () => {
+const recordData = await useSSrSimpleDataLoader('record', async () => {
   return (await CommonContent.getContentList(new GetContentListParams()
     .setSelfValues({
       order: 'publish_at',
     })
    .setModelId(18)
    .setMainBodyColumnId(316)
-  , 1, 50)).list
+  , 1, 50)).list.map(p => p.toJSON());
 });
 </script>
 
@@ -410,10 +417,12 @@ const recordData = useSimpleDataLoader<GetContentListItem[]>(async () => {
       flex-direction: row;
       flex-wrap: wrap;
 
-      div {
+      .box {
         flex: 1 1 50%;
         margin-bottom: 32px;
         cursor: pointer;
+        color: #fff;
+        text-decoration: none;
 
         h5 {
           font-size: 1rem;

+ 49 - 56
src/pages/inheritor.vue

@@ -30,14 +30,15 @@
                 <div class="content">
                   <h4>{{ stat.title || '\u200b' }}</h4>
                   <div class="descs">
-                    <div 
+                    <a 
                       v-for="(data, key2) in stat.datas"
+                      class="box"
                       :key="key2"
-                      @click="data.onClick"
+                      :href="data.link"
                     >
                       <h5>{{ data.title }}</h5>
                       <p>{{ data.value }}</p>
-                    </div>
+                    </a>
                   </div>
                 </div>
               </div>
@@ -68,9 +69,8 @@
           :desc="overviewsLoader.content.value?.[0]"
           :image="Image9"
           :showExpand="false"
-          :rightItems="heritageData.content.value"
-          @rightItemDefaultClick="(item) => navTo('/news/detail', { id: item.id })"
-          @moreClick="navTo('/inheritor/heritage')"
+          :rightItems="(heritageData.content.value as any)"
+          :moreLink="router.resolve('/inheritor/heritage').href"
         />
       </div>
     </section>
@@ -97,9 +97,8 @@
           :image="Image11"
           :showExpand="false"
           left
-          :rightItems="areaData.content.value"
-          @rightItemDefaultClick="(item) => navTo('/news/detail', { id: item.id })"
-          @moreClick="navTo('/inheritor/block')"
+          :rightItems="(areaData.content.value as any)"
+          :moreLink="router.resolve('/inheritor/block').href"
         />
       </div>
     </section>
@@ -115,9 +114,8 @@
           :desc="overviewsLoader.content.value?.[3]"
           :image="Image10"
           :showExpand="false"
-          :rightItems="minnanyuLoader.content.value"
-          @rightItemDefaultClick="(item) => navTo('/news/detail', { id: item.id })"
-          @moreClick="navTo('/inheritor/language')"
+          :rightItems="(minnanyuLoader.content.value as any)"
+          :moreLink="router.resolve('/inheritor/language').href"
         />
       </div>
     </section>
@@ -140,8 +138,7 @@ import Image10 from '@/assets/images/inheritor/Image10.jpg'
 import Image11 from '@/assets/images/inheritor/Image11.jpg'
 import LeftRightBox from '@/components/parts/LeftRightBox.vue';
 import ThreeImageList from '@/components/parts/ThreeImageList.vue';
-import { usePageAction } from '@/composeable/PageAction';
-import { useSimpleDataLoader } from '@/composeable/SimpleDataLoader';
+import { useSSrSimpleDataLoader } from '@/composeable/SimpleDataLoader';
 import CommonContent, { GetColumListParams, GetContentListParams } from '@/api/CommonContent';
 import SimplePageContentLoader from '@/components/content/SimplePageContentLoader.vue';
 import IndexContent from '@/api/introduction/IndexContent';
@@ -149,7 +146,7 @@ import SeminarContent from '@/api/inheritor/SeminarContent';
 import UnmoveableContent from '@/api/inheritor/UnmoveableContent';
 
 const NO_CONTENT_STRING = '';
-const { navTo } = usePageAction();
+const router = useRouter();
 
 const carouselConfig = {
   itemsToShow: 1,
@@ -161,31 +158,31 @@ const list1 = [
     title: '非遗项目',
     desc: '让文化因传承而永存',
     image: Image1,
-    onClick: () => navTo('/inheritor/projects'),
+    link: router.resolve('/inheritor/projects').href,
   },
   {
     title: '非遗传承人',
     desc: '让文化因传承而永存',
     image: Image2,
-    onClick: () => navTo('/inheritor/inheritor'),
+    link: router.resolve('/inheritor/inheritor').href,
   },
   /* {
     title: '非遗作品',
     desc: '让文化因传承而永存',
     image: Image3,
-    onClick: () => navTo('/inheritor/products'),
+    link: router.resolve('/inheritor/products').href,
   },
   {
     title: '非遗活动',
     desc: '让文化因传承而永存',
     image: Image4,
-    onClick: () => navTo('/inheritor/activity'),
+    link: router.resolve('/inheritor/activity').href,
   }, */
   {
     title: '非遗传习所',
     desc: '让文化因传承而永存',
     image: Image5,
-    onClick: () => navTo('/inheritor/seminar'),
+    link: router.resolve('/inheritor/seminar').href,
   },
 ]
 const list2 = [
@@ -193,46 +190,47 @@ const list2 = [
     title: '不可移动文物',
     desc: '让文化因传承而永存',
     image: Image7,
-    onClick: () => navTo('/inheritor/unmoveable'),
+    link: router.resolve('/inheritor/unmoveable').href,
   },
   {
     title: '可移动文物',
     desc: '让文化因传承而永存',
     image: Image8,
-    onClick: () => navTo('/inheritor/moveable'),
+    link: router.resolve('/inheritor/moveable').href,
   },
   {
     title: '',
     desc: '',
-    image: ''
+    image: '',
+    link: '',
   },
 ]
 
-const areaData = useSimpleDataLoader(async () => 
+const areaData = await useSSrSimpleDataLoader('area', async () => 
  (await CommonContent.getContentList(new GetContentListParams()
     .setModelId(17)
     .setMainBodyColumnId(286)
-  , 1, 6)).list
+  , 1, 6)).list.map(p => p.toJSON())
 )
-const heritageData = useSimpleDataLoader(async () => 
+const heritageData = await useSSrSimpleDataLoader('heritage', async () => 
  (await CommonContent.getContentList(new GetContentListParams()
     .setModelId(17)
     .setMainBodyColumnId(310)
-  , 1, 6)).list
+  , 1, 6)).list.map(p => p.toJSON())
 )
-const blockData = useSimpleDataLoader(async () => 
+const blockData = await useSSrSimpleDataLoader('block', async () => 
  (await CommonContent.getContentList(new GetContentListParams()
     .setModelId(17)
     .setMainBodyColumnId(286)
-  , 1, 6)).list
+  , 1, 6)).list.map(p => p.toJSON())
 )
-const minnanyuLoader = useSimpleDataLoader(async () => 
+const minnanyuLoader = await useSSrSimpleDataLoader('minnanyu', async () => 
  (await CommonContent.getContentList(new GetContentListParams()
     .setModelId(18)
     .setMainBodyColumnId(318)
-  , 1, 6)).list
+  , 1, 6)).list.map(p => p.toJSON())
 )
-const overviewsLoader = useSimpleDataLoader(async () => {
+const overviewsLoader = await useSSrSimpleDataLoader('overviews', async () => {
   return [
     (await IndexContent.getColumList(
       new GetColumListParams()
@@ -257,11 +255,11 @@ const overviewsLoader = useSimpleDataLoader(async () => {
   ]
 });
 
-const statsData = useSimpleDataLoader(async () => {
+const statsData = await useSSrSimpleDataLoader('stats', async () => {
   const data = (await IndexContent.getStats());
   const semiCount = (await SeminarContent.getContentList(new GetContentListParams(), 1, 1)).total;
   const unmoveableCount = (await UnmoveableContent.getContentList(new GetContentListParams(), 1, 1)).total;
-
+  
   return [
     {
       title: '非遗代表性项目',
@@ -270,9 +268,7 @@ const statsData = useSimpleDataLoader(async () => {
         return {
           title: item.level_text,
           value: item.total,
-          onClick: () => {
-            navTo('/inheritor/projects', { level: item.level });
-          },
+          link: router.resolve({ path: '/inheritor/projects', query: { level: item.level } }).href,
         }
       })
     },
@@ -283,9 +279,7 @@ const statsData = useSimpleDataLoader(async () => {
         return {
           title: item.title,
           value: item.total,
-          onClick: () => {
-            navTo('/inheritor/inheritor', { level: item.level });
-          },
+          link: router.resolve({ path: '/inheritor/inheritor', query: { level: item.level } }).href
         }
       })
     },
@@ -296,21 +290,21 @@ const statsData = useSimpleDataLoader(async () => {
         {
           title: '传习所',
           value: semiCount,
-          onClick: () => navTo('/inheritor/seminar'),
+          link: router.resolve({ path: '/inheritor/seminar' }).href,
         },
         {
           title: '传统村落',
           value: data.villageData[0]?.total ?? 0,
-          onClick: () => navTo('/village/index'),
+          link: router.resolve({ path: '/village/index' }).href,
         },
         {
           title: '文物古迹',
           value: unmoveableCount,
-          onClick: () => navTo('/inheritor/unmoveable'),
+          link: router.resolve({ path: '/inheritor/unmoveable' }).href,
         },
       ],
     },
-    /* {
+    /*{
       title: '不可移动文物',
       type: '3',
       datas: data.crData.map((item: any) => {
@@ -321,9 +315,9 @@ const statsData = useSimpleDataLoader(async () => {
       })
     },
     {
-      title: '传习中心',
-      type: '3',
-      datas: data.ichCenter.map((item: any) => {
+      title: '闽南文化重要相关文物古迹',
+      type: '2',
+      datas: data.minnanCr.map((item: any) => {
         return {
           title: item.title,
           value: item.total
@@ -331,9 +325,9 @@ const statsData = useSimpleDataLoader(async () => {
       })
     },
     {
-      title: '闽南文化重要相关文物古迹',
-      type: '2',
-      datas: data.minnanCr.map((item: any) => {
+      title: '重要相关历史风貌区',
+      type: '1',
+      datas: data.historyData.map((item: any) => {
         return {
           title: item.title,
           value: item.total
@@ -341,17 +335,16 @@ const statsData = useSimpleDataLoader(async () => {
       })
     },
     {
-      title: '重要相关历史风貌区',
-      type: '1',
-      datas: data.historyData.map((item: any) => {
+      title: '传习中心',
+      type: '3',
+      datas: data.ichCenter.map((item: any) => {
         return {
           title: item.title,
           value: item.total
         }
       })
-    }, */
-  ]
-
+    },*/
+  ];
 });
 
 const carousel3Config = ref({

+ 28 - 20
src/pages/introduction.vue

@@ -24,8 +24,7 @@
           :desc="overviewsLoader.content.value?.[0]"
           :image="Image1"
           :rightItems="historyData.content.value"
-          @rightItemDefaultClick="(item) => navTo('/news/detail', { id: item.id })"
-          @moreClick="navTo('/introduction/history')"
+          :moreLink="router.resolve({ path: '/introduction/history' }).href"
         />
       </div>
     </section>
@@ -43,8 +42,7 @@
           :image="Image2"
           left
           :rightItems="languageData.content.value"
-          @rightItemDefaultClick="(item) => navTo('/news/detail', { id: item.id })"
-          @moreClick="navTo('/introduction/language')"
+          :moreLink="router.resolve({ path: '/introduction/language' }).href"
         />
       </div>
     </section>
@@ -77,14 +75,13 @@ import CategoryImage6 from '@/assets/images/introduction/CategoryImage6.jpg';
 import CategoryImage7 from '@/assets/images/introduction/CategoryImage7.jpg';
 import LeftRightBox from '@/components/parts/LeftRightBox.vue';
 import ThreeImageList from '@/components/parts/ThreeImageList.vue';
-import { useSimpleDataLoader } from '@/composeable/SimpleDataLoader';
+import { useSSrSimpleDataLoader } from '@/composeable/SimpleDataLoader';
 import { GetColumListParams, GetContentListParams } from '@/api/CommonContent';
 import { NO_CONTENT_STRING } from '@/common/ConstStrings';
-import { usePageAction } from '@/composeable/PageAction';
 import HistoryContent from '@/api/introduction/HistoryContent';
 import LanguageContent from '@/api/introduction/LanguageContent';
 
-const { navTo } = usePageAction();
+const router = useRouter();
 
 const carouselConfig = {
   itemsToShow: 1,
@@ -96,54 +93,65 @@ const list = [
     title: '历史人物',
     desc: '让文化因传承而永存',
     image: CategoryImage1,
-    onClick: () => navTo('/introduction/character'),
+    link: '/introduction/character',
   },
   {
     title: '民间习俗',
     desc: '让文化因传承而永存',
     image: CategoryImage2,
-    onClick: () => navTo('/introduction/custom'),
+    link: '/introduction/custom',
   },
   {
     title: '艺术特色',
     desc: '让文化因传承而永存',
     image: CategoryImage3,
-    onClick: () => navTo('/introduction/feature'),
+    link: '/introduction/feature',
   },
   {
     title: '建筑文化',
     desc: '让文化因传承而永存',
     image: CategoryImage4,
-    onClick: () => navTo('/introduction/building'),
+    link: '/introduction/building',
   },
   {
     title: '饮食文化',
     desc: '让文化因传承而永存',
     image: CategoryImage5,
-    onClick: () => navTo('/introduction/victuals'),
+    link: '/introduction/victuals',
   },
   {
     title: '海洋文化',
     desc: '让文化因传承而永存',
     image: CategoryImage6,
-    onClick: () => navTo('/introduction/sea'),
+    link: '/introduction/sea',
   },
   {
     title: '闽南文化百科',
     desc: '让文化因传承而永存',
     image: CategoryImage7,
-    onClick: () => navTo('/introduction/book'),
+    link: '/introduction/book',
   }
 ]
 
-const historyData = useSimpleDataLoader(async () => 
- (await HistoryContent.getContentList(new GetContentListParams().setMainBodyColumnId([ 233, 250, 251 ]), 1, 6)).list
+const historyData = await useSSrSimpleDataLoader('history', async () => 
+ (await HistoryContent.getContentList(new GetContentListParams().setMainBodyColumnId([ 233, 250, 251 ]), 1, 6)).list.map(p => ({
+    id: p.id,
+    title: p.title,
+    desc: p.desc,
+    image: p.image,
+    link: router.resolve({ path: '/news/detail', query: { id: p.id } }).href,
+ }))
 )
-const languageData = useSimpleDataLoader(async () => 
- (await LanguageContent.getContentList(new GetContentListParams(), 1, 6)).list
+const languageData = await useSSrSimpleDataLoader('language', async () => 
+ (await LanguageContent.getContentList(new GetContentListParams(), 1, 6)).list.map(p => ({
+    id: p.id,
+    title: p.title,
+    desc: p.desc,
+    image: p.image,
+    link: router.resolve({ path: '/news/detail', query: { id: p.id } }).href,
+ }))
 )
-
-const overviewsLoader = useSimpleDataLoader(async () => {
+const overviewsLoader = await useSSrSimpleDataLoader('overviews', async () => {
   return [
     (await IndexContent.getColumList(
       new GetColumListParams()

+ 8 - 10
src/pages/news.vue

@@ -60,11 +60,11 @@
       <div class="content news-list">
         <!-- 新闻列表 -->
         <SimplePageContentLoader :loader="newsLoader">
-          <div 
+          <NuxtLink
             v-for="(item, k) in newsLoader.list.value"
             :key="item.id"
             class="item"
-            @click="router.push({ path: '/news/detail', query: { id: item.id }})"
+            :to="{ path: '/news/detail', query: { id: item.id }}"
           >
             <img :src="item.image" alt="新闻图片" />
             <TitleDescBlock
@@ -72,24 +72,23 @@
               :desc="item.desc || item.title"
               :date="DateUtils.formatDate(item.publishAt, DateUtils.FormatStrings.YearCommonShort)"
             />
-          </div>
+          </NuxtLink>
         </SimplePageContentLoader>
         <!-- 分页 -->
         <Pagination2
           v-model:currentPage="newsLoader.page.value"
           :totalPages="newsLoader.totalPages.value"
+          :ssrUrl="router.resolve(route).href"
         />
       </div>
     </section>
-
-
   </div>
 </template>
 
 <script setup lang="ts">
 import { Carousel, Slide, Pagination } from 'vue3-carousel'
 import { onMounted, ref, watch } from 'vue';
-import { useSimplePagerDataLoader } from '@/composeable/SimplePagerDataLoader';
+import { useSSrSimplePagerDataLoader } from '@/composeable/SimplePagerDataLoader';
 import { useRouter } from 'vue-router';
 import Dropdown from '@/components/controls/Dropdown.vue';
 import SimpleInput from '@/components/controls/SimpleInput.vue';
@@ -114,6 +113,7 @@ const mainTabs = [
 const mainTabActive = ref(0);
 
 const router = useRouter();
+const route = useRoute();
 
 const searchValue = ref('');
 const searchRegion = ref(0); 
@@ -121,7 +121,7 @@ const regionData = ref([
   { title: '不限', value: 0  },
 ]);
 
-const newsLoader = useSimplePagerDataLoader(10, async (page, pageSize) => {
+const newsLoader = await useSSrSimplePagerDataLoader('news', Number(route.query.page as string || 1), 10, async (page, pageSize) => {
   let res
   switch(mainTabActive.value) {
     default:
@@ -143,9 +143,8 @@ const newsLoader = useSimplePagerDataLoader(10, async (page, pageSize) => {
       , page, pageSize);
       break;
   }
-
   return {
-    data: res.list,
+    data: res.list.map(p => p.toJSON()),
     total: res.total,
   };
 });
@@ -158,7 +157,6 @@ watch(searchRegion, () => {
 });
 
 onMounted(async () => {
-  newsLoader.loadData(undefined, true);
   regionData.value = [
     { title: '不限', value: 0  },
     ...((await CommonContent.getCategoryList(1)).map(p => ({

+ 9 - 19
src/pages/research.vue

@@ -27,7 +27,6 @@
 
 <script setup lang="ts">
 import { Carousel, Slide, Pagination, Navigation } from 'vue3-carousel'
-import { usePageAction } from '@/composeable/PageAction';
 import Image1 from '@/assets/images/research/Image1.jpg';
 import Image2 from '@/assets/images/research/Image2.jpg';
 import Image3 from '@/assets/images/research/Image3.jpg';
@@ -35,6 +34,8 @@ import Image4 from '@/assets/images/research/Image4.jpg';
 import Image5 from '@/assets/images/research/Image5.jpg';
 import ThreeImageList from '@/components/parts/ThreeImageList.vue';
 
+const router = useRouter();
+
 const carouselConfig = {
   itemsToShow: 1,
   wrapAround: true,
@@ -46,50 +47,39 @@ const list = [
     title: '研究团队',
     desc: '让文化因传承而永存',
     image: Image1,
-    onClick: () => {
-      navTo('/research/teams');
-    }
+    link: router.resolve('/research/teams').href,
   },
   {
     title: '研究项目',
     desc: '让文化因传承而永存',
     image: Image2,
-    onClick: () => {
-      navTo('/research/projects');
-    }
+    link: router.resolve('/research/projects').href,
   },
   {
     title: '理论研讨',
     desc: '让文化因传承而永存',
     image: Image3,
-    onClick: () => {
-      navTo('/research/discuss');
-    }
+    link: router.resolve('/research/discuss').href,
   },
   {
     title: '研究成果',
     desc: '让文化因传承而永存',
     image: Image4,
-    onClick: () => {
-      navTo('/research/result');
-    }
+    link: router.resolve('/research/result').href,
   },
   {
     title: '专家学者',
     desc: '让文化因传承而永存',
     image: Image5,
-    onClick: () => {
-      navTo('/research/expert');
-    }
+    link: router.resolve('/research/expert').href,
   },
   {
     title: '',
     desc: '',
     image: '',
+    link: '',
   }
-]
-
-const { navTo } = usePageAction();
+];
 </script>
 
 <style lang="scss">