LK 博客
前端实战总结——Vue3 + Vite 前端框架
前后端
约 1 分钟阅读 0 赞 0 条评论 鸿蒙黑体

前端实战总结——Vue3 + Vite 前端框架

栗树林在山岗
栗树林在山岗 @栗树林在山岗
累计点赞 0 登录后每个账号只能点一次
内容长度 0 正文词元数
正文
目录会跟随阅读位置移动。
阅读进度

一、项目初始化

1.环境准备

# 检查 Node.js 版本(需要 18+)
node -v

# 创建 Vite + Vue 项目
npm create vite@latest plant-disease-frontend -- --template vue

# 进入项目目录
cd plant-disease-frontend

# 安装依赖
npm install

2.安装核心依赖

# 路由管理
npm install vue-router

# 状态管理
npm install pinia

# 图表库(环形进度图)
npm install echarts

2.项目结构规划

#主项目名

├── fronted/ # 前端项目目录
│ ├── index.html # HTML入口文件
│ ├── package.json # 项目依赖配置
│ ├── vite.config.js # Vite构建配置
│ ├── src/ # 源代码目录
│ │ ├── main.js # 应用入口,初始化Vue、Pinia、Router
│ │ ├── App.vue # 根组件,包含路由视图
│ │ ├── api/ # API接口层
│ │ │ └── index.js # 统一API接口定义
│ │ ├── router/ # 路由配置
│ │ │ └── index.js # 路由定义和导航守卫
│ │ ├── stores/ # Pinia状态管理
│ │ │ └── User.js # 用户状态管理
│ │ ├── styles/ # 全局样式
│ │ │ └── monet-theme.css  
│ │ └── views/ # 页面组件
│ │ ├── Login.vue # 用户登录页面
│ │ ├── Workbench.vue # 工作台布局框架
│ │ ├── Recognize.vue # 病害识别功能页
│ │ ├── Annotation.vue # 标注与训练功能页
│ │ ├── Models.vue # 模型资产管理页
│ │ └── Admin.vue # 平台管理页(管理员专用)
│ └── dist/ # 构建输出目录(可重新生成)
└── .idea/ # IDE配置目录

二、路由配置与权限控制

1.用户状态Store

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  { path: '/login', component: () => import('@/views/Login.vue') },
  {
    path: '/',
    component: () => import('@/views/Workbench.vue'),
    meta: { requiresAuth: true },
    children: [
      { path: 'recognize', component: () => import('@/views/Recognize.vue') },
      { path: 'annotation', component: () => import('@/views/Annotation.vue') },
      { path: 'models', component: () => import('@/views/Models.vue') },
      { path: 'admin', component: () => import('@/views/Admin.vue'), meta: { requiresAdmin: true } }
    ]
  }
]

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

// 导航守卫:权限控制
router.beforeEach((to, from, next) => {
  const token = localStorage.getItem('token')

  if (to.meta.requiresAuth && !token) {
    next('/login')
  } else if (to.meta.requiresAdmin) {
    const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}')
    userInfo.role === 'admin' ? next() : next('/recognize')
  } else {
    next()
  }
})

export default router

三、状态管理(Pinia)

1.用户状态store

// src/stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  // State(状态)
  const token = ref(localStorage.getItem('token') || '')
  const userInfo = ref(JSON.parse(localStorage.getItem('userInfo') || '{}'))

  // Getters(计算属性)
  const isLoggedIn = computed(() => !!token.value)
  const isAdmin = computed(() => userInfo.value.role === 'admin')

  // Actions(方法)
  function setAuth(data) {
    token.value = data.token
    userInfo.value = data.user
    localStorage.setItem('token', data.token)
    localStorage.setItem('userInfo', JSON.stringify(data.user))
  }

  function logout() {
    token.value = ''
    userInfo.value = {}
    localStorage.removeItem('token')
    localStorage.removeItem('userInfo')
  }

  return { token, userInfo, isLoggedIn, isAdmin, setAuth, logout }
})

2.在组件中使用

<script setup>
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

// 读取状态
console.log(userStore.isAdmin)

// 调用方法
userStore.logout()
</script>

3.Pinia核心概念

Pinia核心概念

四、Api接口封装

1.请求统一函数

// src/api/index.js
const API_BASE = '/api'

async function request(url, options = {}) {
  const token = localStorage.getItem('token')

  const response = await fetch(`${API_BASE}${url}`, {
    headers: {
      'Content-Type': 'application/json',
      ...(token && { 'X-Auth-Token': token })
    },
    ...options
  })

  const result = await response.json()

  if (!response.ok) {
    throw new Error(result.message || '请求失败')
  }

  return result
}

2.模块化接口

// 认证接口
export const authApi = {
  login(username, password) {
    return request('/auth/login', {
      method: 'POST',
      body: JSON.stringify({ username, password })
    })
  },

  register(username, password) {
    return request('/auth/register', {
      method: 'POST',
      body: JSON.stringify({ username, password })
    })
  }
}

// 模型接口
export const modelApi = {
  getList() {
    return request('/models')
  },

  upload(name, file) {
    const formData = new FormData()
    formData.append('name', name)
    formData.append('file', file)

    return fetch(`${API_BASE}/models/upload`, {
      method: 'POST',
      headers: { 'X-Auth-Token': localStorage.getItem('token') },
      body: formData
    }).then(res => res.json())
  }
}

3.封装优势

封装优势

五、组件开发

1.登陆页面组件

<!-- src/views/Login.vue -->
<template>
  <div class="login-container">
    <div class="login-card">
      <h1>植物病害识别工作台</h1>

      <div class="tabs">
        <button :class="{ active: isLogin }" @click="isLogin = true">登录</button>
        <button :class="{ active: !isLogin }" @click="isLogin = false">注册</button>
      </div>

      <form @submit.prevent="handleSubmit">
        <input v-model="username" placeholder="用户名" required>
        <input v-model="password" type="password" placeholder="密码" required>
        <button type="submit" :disabled="loading">
          {{ loading ? '处理中...' : (isLogin ? '登录' : '注册') }}
        </button>
      </form>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { authApi } from '@/api'

const router = useRouter()
const userStore = useUserStore()

const isLogin = ref(true)
const username = ref('')
const password = ref('')
const loading = ref(false)

const handleSubmit = async () => {
  loading.value = true
  try {
    const result = isLogin.value
      ? await authApi.login(username.value, password.value)
      : await authApi.register(username.value, password.value)

    userStore.setAuth(result)
    router.push('/recognize')
  } catch (err) {
    alert(err.message)
  } finally {
    loading.value = false
  }
}
</script>

2.组件设计要点

组件设计要点

六、响应式数据

1.ref vs reactive

import { ref, reactive } from 'vue'

// ref:基本类型 + 对象(推荐)
const count = ref(0)        // 包装成 { value: 0 }
count.value++               // 需要 .value 访问

// reactive:仅对象
const user = reactive({
  name: '张三',
  age: 18
})
user.name = '李四'          // 直接访问

2.计算属性

import { ref, computed } from 'vue'

const todos = ref([
  { text: '学习 Vue', done: true },
  { text: '写项目', done: false }
])

const completedCount = computed(() => {
  return todos.value.filter(t => t.done).length
})

// 可写的计算属性
const fullName = computed({
  get: () => firstName.value + ' ' + lastName.value,
  set: (val) => {
    [firstName.value, lastName.value] = val.split(' ')
  }
})

3.侦听器

import { ref, watch, watchEffect } from 'vue'

const keyword = ref('')

// 懒执行,只在变化时触发
watch(keyword, (newVal, oldVal) => {
  console.log(`从 ${oldVal} 变为 ${newVal}`)
})

// 立即执行,自动收集依赖
watchEffect(() => {
  console.log(`当前搜索词: ${keyword.value}`)
})

七、常用命令

1.开发命令

npm run dev	//启动开发服务器
npm run build	 //打包生产版本
npm run preview	//预览打包结果

2.Git常用命令

# 查看状态
git status

# 添加文件
git add .

# 提交
git commit -m "feat: 添加登录功能"

# 推送
git push origin main

# 拉取
git pull origin main

八、常见错误

1.跨域问题

Access-Control-Allow-Origin 错误

解决方案:配置 Vite 代理

// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8000',
        changeOrigin: true
      }
    }
  }
})

2.路由刷新 404

刷新页面后找不到路由

解决方案:配置 nginx 或使用 hash 模式

// hash 模式(带 # 号)
const router = createRouter({
  history: createWebHashHistory(),  // 改为 hash 模式
  routes
})

3.图片上传预览

const handleImageUpload = (e) => {
  const file = e.target.files[0]
  if (file) {
    // 创建预览 URL
    imageUrl.value = URL.createObjectURL(file)
    // 使用完毕后释放内存
    onUnmounted(() => {
      URL.revokeObjectURL(imageUrl.value)
    })
  }
}

九、项目总结

技术:Vue3 用途:前端框架

技术:Vite 用途:构建工具

技术:Vue Router 用途:路由管理

技术:Pinia 用途:状态管理

技术:Fetch API HTTP 用途:请求

作者名片

栗树林在山岗
栗树林在山岗
@栗树林在山岗

这个作者暂时还没有填写个人简介。

评论区
文章作者和管理员都可以管理这里的评论。
0 条评论
登录后即可参与评论。 去登录
还没有评论,欢迎留下第一条交流内容。