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

Angular 13中如何通过路由创建携带文章标题的动态URL?

博客单篇文章动态URL实现方案

核心思路是将原静态详情页路由改为带动态路径参数的路由,将文章标题处理为URL友好的字符串(slug)作为路径段,实现每篇文章对应独立访问地址,整体分为4个核心步骤:

核心实现步骤

  • 生成URL兼容的文章slug:将文章标题做格式化处理,替换空格、特殊字符为短横线,统一大小写,避免URL出现非法字符
  • 替换原有静态路由规则:将原/post-detail静态路由修改为带slug参数的动态路由格式
  • 列表页跳转时拼接动态路径:渲染文章列表时,为每篇文章生成带对应slug的跳转链接
  • 详情页匹配参数渲染内容:进入详情页时从URL中提取slug参数,匹配对应文章内容,匹配失败时跳转404页面

通用工具函数

先写一个通用的slug生成函数,兼容中英文标题:

// utils/slug.js
export function generateSlug(title) {
  return title
    .trim()
    .toLowerCase()
    // 匹配非中文、非字母、非数字的字符,替换为短横线
    .replace(/[^\w\u4e00-\u9fa5]+/g, '-')
    // 清除首尾多余的短横线
    .replace(/^-+|-+$/g, '')
}

常见框架示例代码

Vue3 + Vue Router 实现

  1. 路由配置修改
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import PostList from '../views/PostList.vue'
import PostDetail from '../views/PostDetail.vue'
import NotFound from '../views/NotFound.vue'

const routes = [
  { path: '/', component: PostList },
  // 原静态路由 { path: '/post-detail', component: PostDetail } 替换为动态路由
  { path: '/post/:slug', component: PostDetail },
  { path: '/:pathMatch(.*)*', component: NotFound }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router
  1. 文章列表跳转写法
<!-- views/PostList.vue -->
<template>
  <div class="post-list">
    <article v-for="post in postList" :key="post.id" class="post-card">
      <h3>
        <router-link :to="`/post/${generateSlug(post.title)}`">
          {{ post.title }}
        </router-link>
      </h3>
      <p>{{ post.excerpt }}</p>
    </article>
  </div>
</template>

<script setup>
import { generateSlug } from '../utils/slug'
// 实际场景替换为接口/本地数据源拉取的文章列表
const postList = [
  { id: 1, title: 'Vue3路由配置指南', content: '...' },
  { id: 2, title: 'React Hooks最佳实践', content: '...' }
]
</script>
  1. 详情页参数匹配
<!-- views/PostDetail.vue -->
<template>
  <div v-if="currentPost" class="post-detail">
    <h1>{{ currentPost.title }}</h1>
    <div class="post-content">{{ currentPost.content }}</div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { generateSlug } from '../utils/slug'
import { postList } from '../mock/postData' // 替换为实际数据源

const route = useRoute()
const router = useRouter()
const currentPost = ref(null)

onMounted(() => {
  const targetSlug = route.params.slug
  // 匹配对应文章
  const targetPost = postList.find(post => generateSlug(post.title) === targetSlug)
  if (!targetPost) {
    router.replace('/404')
    return
  }
  currentPost.value = targetPost
})
</script>

React + React Router v6 实现

  1. 路由配置修改
// App.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import PostList from './pages/PostList'
import PostDetail from './pages/PostDetail'
import NotFound from './pages/NotFound'

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<PostList />} />
        {/* 原静态路由<Route path="/post-detail" element={<PostDetail />} /> 替换 */}
        <Route path="/post/:slug" element={<PostDetail />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  )
}

export default App
  1. 列表页跳转写法
// pages/PostList.jsx
import { Link } from 'react-router-dom'
import { generateSlug } from '../utils/slug'
import { postList } from '../mock/postData'

export default function PostList() {
  return (
    <div className="post-list">
      {postList.map(post => (
        <article key={post.id} className="post-card">
          <h3>
            <Link to={`/post/${generateSlug(post.title)}`}>
              {post.title}
            </Link>
          </h3>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  )
}
  1. 详情页参数匹配
// pages/PostDetail.jsx
import { useEffect, useState } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { generateSlug } from '../utils/slug'
import { postList } from '../mock/postData'

export default function PostDetail() {
  const { slug } = useParams()
  const navigate = useNavigate()
  const [currentPost, setCurrentPost] = useState(null)

  useEffect(() => {
    const targetPost = postList.find(post => generateSlug(post.title) === slug)
    if (!targetPost) {
      navigate('/404', { replace: true })
      return
    }
    setCurrentPost(targetPost)
  }, [slug])

  if (!currentPost) return null

  return (
    <div className="post-detail">
      <h1>{currentPost.title}</h1>
      <div className="post-content">{currentPost.content}</div>
    </div>
  )
}

避坑提示:如果存在标题重名的文章,仅用标题生成slug会出现匹配冲突,可以在slug前拼接文章唯一ID,格式如/post/1-vue3-lu-you-pei-zhi-zhi-nan,匹配时提取ID字段查询文章即可,兼顾URL可读性和唯一性。

内容的提问来源于stack exchange,提问作者Sajid Hussain

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 02:57:33