6.11

微头条Router工程代码

一、项目初始化步骤

1. 创建Vite+Vue3项目

# 创建vite项目(新版命令)
npm create vite@latest weitoutiao -- --template vue
# 进入项目目录
cd weitoutiao
# 安装基础依赖
npm install
# 安装路由vue-router
npm install vue-router@4
# 启动项目
npm run dev

2. 项目目录关键文件说明

src/
├─ router/
│  └─ index.js      # 路由配置、路由守卫
├─ views/
│  ├─ Login.vue     # 登录页面
│  └─ Home.vue      # 首页微头条发布+列表
├─ App.vue          # 路由出口容器
└─ main.js          # 入口文件,挂载路由

二、路由配置 src/router/index.js

import { createRouter, createWebHistory } from 'vue-router'
// 导入页面组件
import Login from '../views/Login.vue'
import Home from '../views/Home.vue'

// 路由规则数组
const routes = [
  // 根路径重定向到登录页
  { path: '/', redirect: '/login' },
  // 登录路由
  { path: '/login', component: Login },
  // 首页路由
  { path: '/home', component: Home }
]

// 创建路由实例
const router = createRouter({
  // 使用history模式(无#号)
  history: createWebHistory(),
  routes
})

// 全局前置路由守卫:未登录拦截首页
router.beforeEach((to, from, next) => {
  // 从本地存储获取登录凭证token
  const token = localStorage.getItem('token')
  console.log("触发路由守卫")
  // 访问首页且无token,强制跳转登录
  if (to.path === '/home' && !token) {
    next('/login')
  } else {
    // 正常放行
    next()
  }
})

export default router

三、登录页面 src/views/Login.vue

<template>
    <div class="login-page">
        <div class="login-card">
            <h2>微头条登录</h2>
            <input type="text" placeholder="用户名" v-model="username" />
            <input type="password" placeholder="密码" v-model="password" />
            <button @click="handleLogin">登录</button>
            <button @click="clearForm">清空</button>
            <div class="register-link">
                <a href="#">立即注册</a>
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
// 引入路由跳转方法
import { useRouter } from 'vue-router'

// 双向绑定表单数据
const username = ref('')
const password = ref('')
// 获取路由实例
const router = useRouter()

// 登录逻辑
const handleLogin = () => {
    // 简易固定账号密码校验
    if (username.value === 'w' && password.value === '1') {
        // 登录成功,存储token到本地存储
        localStorage.setItem('token', 'fake-token')
        // 跳转到首页
        router.push('/home')
    } else {
        alert('用户名或密码错误')
    }
}

// 清空表单
const clearForm = () => {
    username.value = ''
    password.value = ''
}

// 页面挂载时禁止页面滚动
onMounted(() => {
    document.body.style.overflow = 'hidden'
})
// 页面销毁恢复滚动
onUnmounted(() => {
    document.body.style.overflow = ''
})
</script>

<style scoped>
.login-page {
    height: 100vh;
    width: 100%;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    display: flex;
    justify-content: center;
    align-items: center;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

.login-card {
    width: 360px;
    background: white;
    padding: 30px;
    border-radius: 16px;
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}

.login-card h2 {
    text-align: center;
    margin-bottom: 24px;
    color: #333;
}

.login-card input {
    width: 100%;
    padding: 12px;
    margin-bottom: 16px;
    border: 1px solid #ddd;
    border-radius: 8px;
    font-size: 14px;
    box-sizing: border-box;
    transition: border-color 0.2s;
}

.login-card input:focus {
    outline: none;
    border-color: #667eea;
}

.login-card button {
    width: 100%;
    padding: 12px;
    border: none;
    border-radius: 8px;
    font-size: 16px;
    cursor: pointer;
    transition: background 0.3s;
}

.login-card button:first-of-type {
    background: #667eea;
    color: white;
}

.login-card button:first-of-type:hover {
    background: #5a67d8;
}

.login-card button:last-of-type {
    background: #e2e8f0;
    color: #333;
    margin-top: 8px;
}

.login-card button:last-of-type:hover {
    background: #cbd5e0;
}

.register-link {
    text-align: center;
    margin-top: 20px;
    font-size: 14px;
    color: #666;
}

.register-link a {
    color: #667eea;
    text-decoration: none;
}

.register-link a:hover {
    text-decoration: underline;
}
</style>

四、首页微头条页面 src/views/Home.vue

<template>
    <div class="home-container">
        <!-- 固定顶部导航栏 -->
        <div class="header">
            <h2>微头条首页</h2>
            <button @click="logout">退出登录</button>
        </div>

        <!-- 滚动内容区域 -->
        <div class="scroll-area">
            <div class="content-wrapper">
                <!-- 发布微头条区域 -->
                <div class="publish-area">
                    <input type="text" v-model="newTitle" placeholder="新闻标题" @keyup.enter="addNews" />
                    <input type="text" v-model="newContent" placeholder="新闻内容" />
                    <button @click="addNews">+ 发布微头条</button>
                </div>

                <!-- 头条列表区域 -->
                <div class="news-list">
                    <div v-if="newsList.length === 0" class="empty">
                        暂无新闻,快去发布一条吧
                    </div>
                    <div v-for="item in newsList" :key="item.id" class="news-item">
                        <h3>{{ item.title }}</h3>
                        <div class="time">{{ item.time }}</div>
                        <div class="content">{{ item.content }}</div>
                        <div class="actions">
                            <a href="javascript:;">📚 查看详情</a>
                            <button @click="deleteNews(item.id)">🗑 删除</button>
                            <button @click="likeNews(item.id)">💗 点赞 {{ item.likes }}</button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script setup>
import { useRouter } from 'vue-router'
import { ref, onMounted, onUnmounted } from 'vue'

// 头条列表模拟数据
const newsList = ref([
    {
        id: 1,
        title: '微头条1.0 正式发布',
        time: '2025-06-01 10:30',
        content: '微头条是一个轻量级信息分享平台,采用 Vue3 + SpringBoot 技术栈。',
        likes: 0
    },
    {
        id: 2,
        title: 'Vue3 组合式 API 学习心得',
        time: '2025-06-02 15:20',
        content: 'setup 语法糖非常简洁,ref和reactive让数据响应式变得容易。',
        likes: 2
    }
])

// 发布表单绑定数据
const newTitle = ref('')
const newContent = ref('')
// 自增ID
let nextId = 3

// 发布头条
const addNews = () => {
    if (!newTitle.value.trim()) {
        alert('请输入标题')
        return
    }
    const newNews = {
        id: nextId++,
        title: newTitle.value,
        content: newContent.value || '暂无内容',
        time: new Date().toLocaleString(),
        likes: 0
    }
    // 头部插入新头条
    newsList.value.unshift(newNews)
    // 清空输入框
    newTitle.value = ''
    newContent.value = ''
}

// 删除头条
const deleteNews = (id) => {
    newsList.value = newsList.value.filter(item => item.id !== id)
}

// 点赞功能
const likeNews = (id) => {
    const item = newsList.value.find(item => item.id === id)
    if (item) {
        item.likes++
    }
}

const router = useRouter()
// 退出登录
const logout = () => {
    // 清除本地token
    localStorage.removeItem('token')
    // 跳转登录页
    router.push('/login')
}

// 页面生命周期控制滚动条
onMounted(() => {
    document.body.style.overflow = 'hidden'
})

onUnmounted(() => {
    document.body.style.overflow = ''
})
</script>

<style scoped>
/* 整体容器:占满视口,flex 纵向排列 */
.home-container {
    height: 100vh;
    display: flex;
    flex-direction: column;
    background: #f5f5f5;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

/* 固定头部 */
.header {
    flex-shrink: 0;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 16px 24px;
    background: #ffffff;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.02);
    border-bottom: 5px solid #e0e0e0;
}

.header h2 {
    margin: 0;
    color: #333;
}

.header button {
    background: #ff4d4f;
    color: white;
    border: none;
    border-radius: 20px;
    padding: 6px 16px;
    cursor: pointer;
}

.header button:hover {
    background: #ff7875;
}

/* 滚动区域:占据剩余高度,内部滚动 */
.scroll-area {
    flex: 1;
    overflow-y: auto;
    padding: 20px 20px 20px 20px;
}

/* 内容包装器:限制最大宽度并居中 */
.content-wrapper {
    max-width: 800px;
    margin: 0 auto;
}

/* 发布区域 */
.publish-area {
    display: flex;
    gap: 10px;
    margin-top: 20px;
    margin-bottom: 20px;
}

.publish-area input {
    flex: 1;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 24px;
}

.publish-area button {
    display: block;
    width: 180px;
    background: #1890ff;
    color: white;
    border: none;
    border-radius: 24px;
    padding: 10px 0;
    font-size: 16px;
    cursor: pointer;
    transition: background 0.3s;
}

.publish-area button:hover {
    background: #0c7bdf;
}

/* 新闻列表 */
.news-list {
    margin-top: 20px;
}

/* 新闻卡片 */
.news-item {
    background: white;
    border-radius: 12px;
    padding: 20px;
    margin-bottom: 16px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
    transition: transform 0.2s, box-shadow 0.2s;
}

.news-item:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.news-item h3 {
    margin-bottom: 8px;
    color: #333;
}

.time {
    font-size: 12px;
    color: #999;
    margin-bottom: 12px;
}

.content {
    font-size: 14px;
    color: #666;
    line-height: 1.5;
    margin-bottom: 12px;
}

.actions a {
    color: #1890ff;
    text-decoration: none;
    margin-right: 10px;
    font-size: 14px;
}

.actions a:hover {
    text-decoration: underline;
}

.actions button {
    margin-right: 10px;
    background: none;
    border: none;
    color: #e5152d;
    cursor: pointer;
}

.actions button:hover {
    text-decoration: underline;
}

.actions button:last-child {
    color: #0ae411;
}

.empty {
    text-align: center;
    color: #999;
    padding: 40px;
}
</style>

五、根组件 App.vue

<template>
    <!-- 路由视图出口,匹配路由自动渲染对应页面 -->
    <router-view />
</template>

<style>
/* 全局样式重置 */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
</style>

六、项目入口 main.js

import { createApp } from 'vue'
import App from './App.vue'
// 导入路由实例
import router from './router'

// 创建Vue应用
const app = createApp(App)
// 注册路由插件
app.use(router)
// 挂载到页面#app节点
app.mount('#app')

七、核心功能知识点总结

  1. VueRouter4基础
    • createRouter 创建路由实例
    • createWebHistory() history无#模式
    • useRouter() 获取路由实例实现编程式导航 push()
  2. 路由守卫
    • beforeEach 全局前置守卫,登录鉴权拦截
    • to 目标路由、from 当前路由、next() 放行/跳转
  3. 本地存储 localStorage
    • setItem 存储token、getItem 获取、removeItem 删除
  4. 页面功能
    • 登录校验、退出清除登录状态
    • 微头条发布、删除、点赞
    • 列表渲染 v-for + key、空数据判断 v-if
posted @ 2026-06-18 11:55  裕达  阅读(17)  评论(0)    收藏  举报