Browse Source

feat(frontend): API 分层和 workspace Pinia store

wangkangyjy 1 week ago
parent
commit
b7b25c96f7

+ 52 - 0
frontend/src/api/client.js

@@ -0,0 +1,52 @@
+import axios from 'axios'
+import { ElMessage } from 'element-plus'
+
+const client = axios.create({
+  baseURL: '/api',
+  timeout: 15000,
+  headers: { 'Content-Type': 'application/json' }
+})
+
+// Request interceptor — attach JWT token
+client.interceptors.request.use(config => {
+  const token = localStorage.getItem('accessToken')
+  if (token) {
+    config.headers.Authorization = `Bearer ${token}`
+  }
+  return config
+})
+
+// Response interceptor — normalize errors
+client.interceptors.response.use(
+  response => {
+    const { data } = response
+    // ApiResult wrapper
+    if (data && data.code !== undefined) {
+      if (data.code === 200) return data
+      if (data.code === 401) {
+        localStorage.removeItem('accessToken')
+        window.location.hash = '#/login'
+        return Promise.reject(new Error('登录已过期'))
+      }
+      return Promise.reject({ ...data, _handled: true })
+    }
+    return data
+  },
+  error => {
+    const { response } = error
+    if (response && response.status === 409) {
+      return Promise.reject({
+        code: 409,
+        message: '数据已被他人修改,请刷新后重试',
+        conflictVersion: response.data?.conflictVersion,
+        _handled: true
+      })
+    }
+    if (!error._handled) {
+      ElMessage.error(error.message || '请求失败')
+    }
+    return Promise.reject(error)
+  }
+)
+
+export default client

+ 29 - 0
frontend/src/api/okr.js

@@ -0,0 +1,29 @@
+import client from './client'
+
+export function getWorkbench(periodId) {
+  return client.get('/okr/workbench', { params: { periodId } })
+}
+
+export function saveDraft(data) {
+  return client.post('/okr/draft', data)
+}
+
+export function submitOkr(data) {
+  return client.post('/okr/submit', data)
+}
+
+export function reviewOkr(data) {
+  return client.post('/okr/review', data)
+}
+
+export function updateProgress(krId, data) {
+  return client.put(`/okr/kr/${krId}/progress`, data)
+}
+
+export function getMyOkr(periodId) {
+  return client.get('/okr/my', { params: { periodId } })
+}
+
+export function getTeamOkr(periodId) {
+  return client.get('/okr/team', { params: { periodId } })
+}

+ 25 - 0
frontend/src/api/scoring.js

@@ -0,0 +1,25 @@
+import client from './client'
+
+export function getReviewQueue(periodId) {
+  return client.get('/scores/review-queue', { params: { periodId } })
+}
+
+export function getMyResult(periodId) {
+  return client.get('/scores/my', { params: { periodId } })
+}
+
+export function getHistory() {
+  return client.get('/scores/history')
+}
+
+export function submitSelfScore(data) {
+  return client.post('/scores/self', data)
+}
+
+export function submitFinalScore(data) {
+  return client.post('/scores/final', data)
+}
+
+export function publishResult(periodId) {
+  return client.post('/scores/publish', { periodId })
+}

+ 6 - 0
frontend/src/api/workspace.js

@@ -0,0 +1,6 @@
+import client from './client'
+
+export function getOverview(periodId) {
+  const params = periodId ? { periodId } : {}
+  return client.get('/workspace/overview', { params })
+}

+ 65 - 0
frontend/src/stores/workspace.js

@@ -0,0 +1,65 @@
+import { defineStore } from 'pinia'
+import { ref, computed } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import * as workspaceApi from '@/api/workspace'
+
+export const useWorkspaceStore = defineStore('workspace', () => {
+  const currentPeriod = ref(null)
+  const availablePeriods = ref([])
+  const currentPhase = ref(null)
+  const todos = ref([])
+  const allowedActions = ref([])
+  const teamSummary = ref(null)
+  const risks = ref([])
+  const deadlines = ref([])
+  const loading = ref(false)
+  const error = ref(null)
+
+  const hasPeriod = computed(() => !!currentPeriod.value)
+
+  const periodId = computed(() => {
+    // URL takes precedence
+    try {
+      const route = useRoute()
+      if (route.query.periodId) return Number(route.query.periodId)
+    } catch {}
+    return currentPeriod.value?.id || null
+  })
+
+  async function refreshOverview(explicitPeriodId) {
+    loading.value = true
+    error.value = null
+    try {
+      const res = await workspaceApi.getOverview(explicitPeriodId)
+      const data = res.data
+      currentPeriod.value = data.recommendedPeriod
+      availablePeriods.value = data.availablePeriods || []
+      currentPhase.value = data.currentPhase
+      todos.value = data.myTodos || []
+      allowedActions.value = data.allowedActions || []
+      teamSummary.value = data.teamSummary
+      risks.value = data.risks || []
+      deadlines.value = data.upcomingDeadlines || []
+    } catch (e) {
+      error.value = e.message || '加载工作台失败'
+    } finally {
+      loading.value = false
+    }
+  }
+
+  async function selectPeriod(id) {
+    try {
+      const router = useRouter()
+      const route = useRoute()
+      router.replace({ query: { ...route.query, periodId: id } })
+    } catch {}
+    await refreshOverview(id)
+  }
+
+  return {
+    currentPeriod, availablePeriods, currentPhase,
+    todos, allowedActions, teamSummary, risks, deadlines,
+    loading, error, hasPeriod, periodId,
+    refreshOverview, selectPeriod
+  }
+})