状态管理与 API 集成
约 587 字大约 2 分钟
布欧-Lewyon
2026-05-15
首页 › Vue › 实战项目 › 状态管理与 API 集成
认证 Store
// stores/auth.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import request from '@/api/request'
import router from '@/router'
export const useAuthStore = defineStore('auth', () => {
const user = ref(null)
const token = ref(localStorage.getItem('token'))
const permissions = ref([])
const isLoggedIn = computed(() => !!token.value)
const isAdmin = computed(() => user.value?.role === 'admin')
function hasPermission(code: string) {
return permissions.value.includes(code) || isAdmin.value
}
async function login(credentials: { username: string; password: string }) {
const data: any = await request.post('/auth/login', credentials)
token.value = data.token
user.value = data.user
permissions.value = data.permissions
localStorage.setItem('token', data.token)
router.push('/dashboard')
}
async function logout() {
token.value = null
user.value = null
permissions.value = []
localStorage.removeItem('token')
router.push('/login')
}
async function fetchUser() {
const data: any = await request.get('/auth/me')
user.value = data.user
permissions.value = data.permissions
}
return { user, token, permissions, isLoggedIn, isAdmin, hasPermission, login, logout, fetchUser }
})看板 Store
// stores/board.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import request from '@/api/request'
import type { Board, Column, Task } from '@/types/board'
export const useBoardStore = defineStore('board', () => {
const boards = ref<Board[]>([])
const currentBoard = ref<Board | null>(null)
const loading = ref(false)
// 获取所有看板
async function fetchBoards() {
loading.value = true
try {
boards.value = await request.get('/boards') as Board[]
} finally {
loading.value = false
}
}
// 获取单个看板详情
async function fetchBoard(id: number) {
loading.value = true
try {
currentBoard.value = await request.get(`/boards/${id}`) as Board
} finally {
loading.value = false
}
}
// 创建看板
async function createBoard(title: string) {
const board = await request.post('/boards', { title }) as Board
boards.value.push(board)
return board
}
// 拖拽移动任务
async function moveTask(taskId: number, targetColumnId: number, order: number) {
await request.patch(`/tasks/${taskId}/move`, {
columnId: targetColumnId,
order
})
// 本地乐观更新
if (currentBoard.value) {
// 从原列移除
for (const col of currentBoard.value.columns) {
col.tasks = col.tasks.filter(t => t.id !== taskId)
}
// 添加到目标列(简化:后面用拖拽库实现)
}
}
return { boards, currentBoard, loading, fetchBoards, fetchBoard, createBoard, moveTask }
})拖拽排序(vuedraggable)
<!-- KanbanColumn.vue -->
<script setup lang="ts">
import draggable from 'vuedraggable'
import type { Column, Task } from '@/types/board'
import { useBoardStore } from '@/stores/board'
defineProps<{ column: Column }>()
const boardStore = useBoardStore()
function onDragEnd(event: any) {
const { item, to, from } = event.detail || event
const taskId = +item.id
const targetColumnId = +to.dataset.columnId
boardStore.moveTask(taskId, targetColumnId, to.children.length)
}
</script>
<template>
<div class="kanban-column" :data-column-id="column.id">
<h3 class="column-header">
{{ column.title }}
<span class="task-count">{{ column.tasks.length }}</span>
</h3>
<draggable
:list="column.tasks"
group="tasks"
item-key="id"
class="task-list"
@change="onDragEnd"
>
<template #item="{ element: task }">
<KanbanCard :task="task" />
</template>
</draggable>
</div>
</template>看板视图
<!-- views/BoardDetail.vue -->
<script setup lang="ts">
import { onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { useBoardStore } from '@/stores/board'
import KanbanColumn from '@/components/kanban/KanbanColumn.vue'
const route = useRoute()
const boardStore = useBoardStore()
const boardId = computed(() => +route.params.id)
onMounted(() => {
boardStore.fetchBoard(boardId.value)
})
</script>
<template>
<div class="board-detail" v-if="boardStore.currentBoard">
<h1>{{ boardStore.currentBoard.title }}</h1>
<div class="columns" v-if="!boardStore.loading">
<KanbanColumn
v-for="col in boardStore.currentBoard.columns"
:key="col.id"
:column="col"
/>
</div>
<div v-else class="loading">加载中...</div>
</div>
</template>小结
| Store | 核心功能 |
|---|---|
| auth | 登录/退出、Token 管理、权限检查 |
| board | 看板 CRUD、拖拽移动任务、乐观更新 |
| vuedraggable | 拖拽排序 + change 事件派发 |
下一节:路由与权限控制
