正文
目录会跟随阅读位置移动。
阅读进度

一、项目初始化
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核心概念

四、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 用途:请求