You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Nuxt3实现带独立URL的单页项目弹窗式作品集方案问询

Nuxt3实现弹窗式项目详情+独立URL+单页体验方案

结合你提到的两种方案的优缺点,这里提供一个整合式的实现思路,同时满足弹窗展示、独立可分享URL、无刷新单页体验三个核心需求:

核心思路

通过路由参数承载项目标识+状态管理控制弹窗显隐+滚动位置保存恢复的组合方式,既利用Nuxt路由生成独立URL,又通过监听路由变化实现弹窗的无缝切换,同时解决滚动体验问题。


具体实现步骤

1. 路由结构设计

保留嵌套路由作为URL载体,但不需要为项目页面单独渲染完整布局:

pages/
-- index.vue       # 主页面:包含着陆区、关于区、项目网格
-- projects/
---- [slug].vue    # 空页面(仅作为路由占位,内容在index.vue的弹窗中加载)

2. 状态管理定义

用Nuxt内置的useState(或Pinia)维护弹窗相关状态:

// 在composables/usePortfolio.js中定义可复用状态
export const usePortfolio = () => {
  const isModalOpen = useState('isModalOpen', () => false)
  const currentProject = useState('currentProject', () => null)
  const scrollPosition = useState('scrollPosition', () => 0)

  return { isModalOpen, currentProject, scrollPosition }
}

3. 项目网格与路由跳转

在index.vue的项目网格中,使用<NuxtLink>跳转至项目路由,同时保存当前滚动位置:

<template>
  <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
    <div v-for="project in projects" :key="project.id" class="project-card">
      <NuxtLink 
        :to="`/projects/${project.slug}`" 
        @click="saveScroll"
      >
        <h3>{{ project.title.rendered }}</h3>
        <img :src="project.featured_media_url" alt="" />
      </NuxtLink>
    </div>
  </div>
</template>

<script setup>
const { scrollPosition } = usePortfolio()
const saveScroll = () => {
  scrollPosition.value = window.scrollY
  // 存入sessionStorage,解决页面刷新后滚动位置丢失问题
  sessionStorage.setItem('portfolioScroll', scrollPosition.value.toString())
}

// 从WordPress API加载项目列表
const { data: projects } = await useFetch('https://你的WP站点/wp-json/wp/v2/projects')
</script>

4. 路由监听与弹窗控制

在index.vue中监听路由变化,动态控制弹窗显隐并加载项目内容:

<template>
  <!-- 主页面内容:着陆区、关于区、项目网格 -->
  
  <!-- 项目详情弹窗 -->
  <div v-if="isModalOpen" class="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4">
    <div class="bg-white rounded-lg p-6 max-w-4xl w-full max-h-[90vh] overflow-y-auto relative">
      <button 
        @click="closeModal"
        class="absolute top-4 right-4 text-gray-500 hover:text-gray-800"
      >
        ✕
      </button>
      <h2 class="text-2xl font-bold mb-4">{{ currentProject?.title.rendered }}</h2>
      <div v-html="currentProject?.content.rendered" class="prose"></div>
    </div>
  </div>
</template>

<script setup>
const { isModalOpen, currentProject, scrollPosition } = usePortfolio()
const route = useRoute()
const router = useRouter()

// 监听路由变化,处理弹窗逻辑
watch(() => route.path, async (newPath) => {
  if (newPath.startsWith('/projects/')) {
    const slug = newPath.split('/projects/')[1]
    // 从WordPress API加载单个项目详情
    const { data: project } = await useFetch(`https://你的WP站点/wp-json/wp/v2/projects?slug=${slug}`)
    currentProject.value = project.value[0]
    isModalOpen.value = true
    // 恢复跳转前的滚动位置,避免页面跳顶
    window.scrollTo(0, scrollPosition.value)
  } else {
    isModalOpen.value = false
  }
})

// 关闭弹窗时返回上一页
const closeModal = () => router.back()

// 处理页面刷新场景:直接访问项目URL时自动打开弹窗
onMounted(() => {
  if (route.path.startsWith('/projects/')) {
    const savedPos = sessionStorage.getItem('portfolioScroll')
    if (savedPos) scrollPosition.value = parseInt(savedPos)
    // 触发路由监听逻辑加载项目
    watch(() => route.path, () => {}, { immediate: true })
  }
})
</script>

5. 禁用路由跳转自动滚动

在nuxt.config.ts中配置滚动行为,避免跳转项目路由时页面自动滚顶:

export default defineNuxtConfig({
  app: {
    scrollBehavior(to, from) {
      // 跳转到项目路由时,保留当前滚动位置
      if (to.path.startsWith('/projects/')) {
        return { top: window.scrollY }
      }
      // 从项目路由返回时,恢复之前的滚动位置
      if (from.path.startsWith('/projects/')) {
        const savedPos = sessionStorage.getItem('portfolioScroll')
        return { top: savedPos ? parseInt(savedPos) : 0 }
      }
      return { top: 0 }
    }
  },
  // 其他配置...
})

方案优势

  • 每个项目拥有独立URL,支持直接分享和浏览器书签
  • 点击项目时无页面刷新,弹窗无缝加载,保持单页体验
  • 关闭弹窗或浏览器前进后退时,自动恢复之前的滚动位置
  • 兼容页面刷新场景,直接访问项目URL会自动打开弹窗并恢复滚动位置

内容的提问来源于stack exchange,提问作者Geert Roks

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 01:07:43