|
|
@@ -1,30 +1,120 @@
|
|
|
<template>
|
|
|
<div class="workspace-view">
|
|
|
- <div class="data-toolbar">
|
|
|
- <span class="data-toolbar__title">工作台</span>
|
|
|
- <span class="data-toolbar__sep">|</span>
|
|
|
- <span class="data-toolbar__meta" v-if="workspace.currentPeriod">
|
|
|
- {{ workspace.currentPeriod.name }} · {{ phaseLabel }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- <div class="workspace-grid">
|
|
|
- <p>工作台页面(将在任务9完善)</p>
|
|
|
- </div>
|
|
|
+ <AsyncState :loading="workspace.loading" :error="!!workspace.error" :error-message="workspace.error"
|
|
|
+ :empty="!workspace.hasPeriod" empty-message="暂无活跃考核周期"
|
|
|
+ @retry="workspace.refreshOverview()">
|
|
|
+ <div class="data-toolbar">
|
|
|
+ <span class="data-toolbar__title">工作台</span>
|
|
|
+ <span class="data-toolbar__sep">|</span>
|
|
|
+ <span class="data-toolbar__meta" v-if="workspace.currentPeriod">
|
|
|
+ {{ workspace.currentPeriod.name }} · {{ phaseLabel }}
|
|
|
+ </span>
|
|
|
+ <span class="data-toolbar__spacer"></span>
|
|
|
+ <el-select v-model="selectedPeriodId" size="small" placeholder="切换周期" style="width:180px"
|
|
|
+ @change="workspace.selectPeriod($event)" v-if="workspace.availablePeriods.length">
|
|
|
+ <el-option v-for="p in workspace.availablePeriods" :key="p.id" :label="p.name" :value="p.id" />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="workspace-grid" v-if="workspace.hasPeriod">
|
|
|
+ <!-- 我的待办 -->
|
|
|
+ <section class="workspace-card">
|
|
|
+ <div class="uppercase-label">我的待办</div>
|
|
|
+ <div v-if="!workspace.todos.length" class="workspace-card__empty">暂无待办</div>
|
|
|
+ <router-link v-for="todo in workspace.todos" :key="todo.type"
|
|
|
+ :to="todo.targetUrl" class="workspace-todo">
|
|
|
+ <span class="workspace-todo__label">{{ todo.label }}</span>
|
|
|
+ <el-tag v-if="todo.count" size="small" type="warning">{{ todo.count }}</el-tag>
|
|
|
+ </router-link>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- 团队汇总 -->
|
|
|
+ <section class="workspace-card" v-if="workspace.teamSummary">
|
|
|
+ <div class="uppercase-label">团队汇总</div>
|
|
|
+ <div class="workspace-summary">
|
|
|
+ <div class="workspace-summary__item">
|
|
|
+ <span class="workspace-summary__num">{{ workspace.teamSummary.totalMembers }}</span>
|
|
|
+ <span class="workspace-summary__desc">团队成员</span>
|
|
|
+ </div>
|
|
|
+ <div class="workspace-summary__item">
|
|
|
+ <span class="workspace-summary__num">{{ workspace.teamSummary.okrSubmitted }}</span>
|
|
|
+ <span class="workspace-summary__desc">已提交 OKR</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- 风险 -->
|
|
|
+ <section class="workspace-card" v-if="workspace.risks.length">
|
|
|
+ <div class="uppercase-label">风险项</div>
|
|
|
+ <div v-for="r in workspace.risks" :key="r.label" class="workspace-risk">
|
|
|
+ <StatusLabel tone="warning" :label="r.label" />
|
|
|
+ <span class="workspace-risk__user" v-if="r.userName">{{ r.userName }}</span>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- 截止时间 -->
|
|
|
+ <section class="workspace-card" v-if="workspace.deadlines.length">
|
|
|
+ <div class="uppercase-label">近期节点</div>
|
|
|
+ <div v-for="d in workspace.deadlines" :key="d.label" class="workspace-deadline">
|
|
|
+ <span>{{ d.label }}</span>
|
|
|
+ <span :class="{ 'workspace-deadline--urgent': d.type === 'URGENT' }">{{ d.date }}</span>
|
|
|
+ </div>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+ </AsyncState>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { computed, onMounted } from 'vue'
|
|
|
+import { computed, onMounted, ref, watch } from 'vue'
|
|
|
import { useWorkspaceStore } from '@/stores/workspace'
|
|
|
+import AsyncState from '@/components/common/AsyncState.vue'
|
|
|
+import StatusLabel from '@/components/common/StatusLabel.vue'
|
|
|
|
|
|
const workspace = useWorkspaceStore()
|
|
|
|
|
|
+const selectedPeriodId = ref(null)
|
|
|
+
|
|
|
const phaseLabel = computed(() => {
|
|
|
const map = { DRAFT: '草稿', OKR_ALIGN: 'OKR 对齐', EXECUTING: '执行中', ASSESSING: '考评中', ARCHIVED: '已归档' }
|
|
|
return map[workspace.currentPhase] || workspace.currentPhase || ''
|
|
|
})
|
|
|
|
|
|
+watch(() => workspace.currentPeriod, (p) => {
|
|
|
+ selectedPeriodId.value = p?.id || null
|
|
|
+}, { immediate: true })
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
workspace.refreshOverview()
|
|
|
})
|
|
|
</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.workspace-view { max-width: 960px; margin: 0 auto; }
|
|
|
+.workspace-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-lg, 16px); margin-top: var(--spacing-lg, 16px); }
|
|
|
+.workspace-card {
|
|
|
+ background: var(--workspace-surface, #fff);
|
|
|
+ border: 1px solid var(--color-border-soft, #edf2f6);
|
|
|
+ border-radius: var(--radius-md, 8px);
|
|
|
+ padding: var(--spacing-lg, 16px);
|
|
|
+}
|
|
|
+.workspace-card__empty { color: var(--color-text-muted); font-size: 13px; padding: 12px 0; }
|
|
|
+.workspace-todo {
|
|
|
+ display: flex; align-items: center; justify-content: space-between;
|
|
|
+ padding: 10px 0; border-bottom: 1px solid var(--color-border-soft);
|
|
|
+ cursor: pointer; text-decoration: none; color: var(--color-text);
|
|
|
+ transition: color var(--motion-fast, 160ms ease);
|
|
|
+}
|
|
|
+.workspace-todo:hover { color: var(--color-brand); }
|
|
|
+.workspace-todo:last-child { border-bottom: none; }
|
|
|
+.workspace-todo__label { font-size: 13px; font-weight: 500; }
|
|
|
+.workspace-summary { display: flex; gap: 32px; padding: 12px 0; }
|
|
|
+.workspace-summary__item { text-align: center; }
|
|
|
+.workspace-summary__num { font-size: 24px; font-weight: 700; color: var(--color-text); display: block; }
|
|
|
+.workspace-summary__desc { font-size: 11px; color: var(--color-text-muted); }
|
|
|
+.workspace-risk { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--color-border-soft); }
|
|
|
+.workspace-risk__user { font-size: 11px; color: var(--color-text-muted); }
|
|
|
+.workspace-deadline { display: flex; justify-content: space-between; padding: 8px 0; font-size: 13px; border-bottom: 1px solid var(--color-border-soft); }
|
|
|
+.workspace-deadline--urgent { color: var(--color-danger); font-weight: 600; }
|
|
|
+@media (max-width: 768px) { .workspace-grid { grid-template-columns: 1fr; } }
|
|
|
+</style>
|