feat(projects): 工作台接口切换为真实数据
This commit is contained in:
@@ -1,19 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { computed, nextTick, onActivated, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import dayjs from 'dayjs';
|
||||
import { OBJECT_CONTEXT_QUERY_KEY } from '@/constants/object-context';
|
||||
import { fetchGetMyWorklogWeek, fetchGetTeamWorklogWeek } from '@/service/api';
|
||||
import { type ECOption, useEcharts } from '@/hooks/common/echarts';
|
||||
import { getWorkbenchItemColor } from '../composables/use-workbench-colors';
|
||||
import {
|
||||
type WorkbenchTeamWorklogView,
|
||||
type WorkbenchWeekWorklogView,
|
||||
type WorkbenchWorklogDistributionItem,
|
||||
buildWorkbenchTeamWorklogView,
|
||||
buildWorkbenchWeekWorklogView
|
||||
} from '../homepage';
|
||||
import { workbenchMyWeekWorklogMock, workbenchTeamWorklogMock } from '../mock';
|
||||
import { useWorkbenchRefresh } from '../composables/use-workbench-refresh';
|
||||
import WorkbenchModuleCard from './workbench-module-card.vue';
|
||||
|
||||
defineOptions({ name: 'WorkbenchMyWeekWorklog' });
|
||||
@@ -26,7 +23,8 @@ defineEmits<{ (e: 'hide'): void }>();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { loading, refresh } = useWorkbenchRefresh();
|
||||
const myWeekData = ref<Api.Project.MyWorklogWeekResult | null>(null);
|
||||
const teamWeekData = ref<Api.Project.TeamWorklogWeekResult | null>(null);
|
||||
|
||||
// EP type='week' 默认 firstDayOfWeek=7,从日历点选时返回当周"周日"。
|
||||
// 我们按 ISO 周(周一-周日)存储;遇到周日 +1 天再 startOf('isoWeek'),避免回退到上一周。
|
||||
@@ -50,25 +48,68 @@ const selectedWeekStart = computed(() => {
|
||||
return aligned ? aligned.format('YYYY-MM-DD') : '';
|
||||
});
|
||||
|
||||
// 周切换必须重拉,不能被"并发守卫"拦掉,故不走 useWorkbenchRefresh:
|
||||
// 自管 loading + 请求序号,旧响应(慢请求落后于新一次切周)直接丢弃
|
||||
const loading = ref(false);
|
||||
let requestSeq = 0;
|
||||
|
||||
async function loadWorklogWeek() {
|
||||
const weekStart = selectedWeekStart.value;
|
||||
if (!weekStart) return;
|
||||
|
||||
requestSeq += 1;
|
||||
const seq = requestSeq;
|
||||
loading.value = true;
|
||||
try {
|
||||
const [myResult, teamResult] = await Promise.all([
|
||||
fetchGetMyWorklogWeek({ weekStart }),
|
||||
fetchGetTeamWorklogWeek({ weekStart })
|
||||
]);
|
||||
|
||||
if (seq !== requestSeq) return;
|
||||
myWeekData.value = myResult.error || !myResult.data ? null : myResult.data;
|
||||
teamWeekData.value = teamResult.error || !teamResult.data ? null : teamResult.data;
|
||||
} finally {
|
||||
if (seq === requestSeq) {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
loadWorklogWeek();
|
||||
}
|
||||
|
||||
type TabKey = 'my' | 'team';
|
||||
const activeTab = ref<TabKey>('my');
|
||||
|
||||
// ============ 我的工时 ============
|
||||
// 周切换(含初始)拉取两 tab 数据;竞态由 loadWorklogWeek 内请求序号兜底
|
||||
watch(selectedWeekStart, loadWorklogWeek, { immediate: true });
|
||||
|
||||
const myView = computed<WorkbenchWeekWorklogView | null>(() => {
|
||||
if (!selectedWeekStart.value) return null;
|
||||
if (selectedWeekStart.value === workbenchMyWeekWorklogMock.current.weekStart) {
|
||||
return buildWorkbenchWeekWorklogView(workbenchMyWeekWorklogMock.current);
|
||||
// 工作台路由 keepAlive:切回时组件不重挂载,immediate watch 不再触发。
|
||||
// 每次激活归位到当前周并重拉;首次激活与挂载同拍(上面 immediate 已拉过),跳过避免双发
|
||||
let activatedOnce = false;
|
||||
onActivated(() => {
|
||||
if (!activatedOnce) {
|
||||
activatedOnce = true;
|
||||
return;
|
||||
}
|
||||
if (selectedWeekStart.value === workbenchMyWeekWorklogMock.previous.weekStart) {
|
||||
return buildWorkbenchWeekWorklogView(workbenchMyWeekWorklogMock.previous);
|
||||
const currentWeekDate = dayjs().startOf('isoWeek');
|
||||
if (selectedWeekStart.value === currentWeekDate.format('YYYY-MM-DD')) {
|
||||
// 周未变时 watch 不会触发,手动重拉取最新填报
|
||||
loadWorklogWeek();
|
||||
} else {
|
||||
selectedWeekDate.value = currentWeekDate.toDate();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const isCurrentWeek = computed(() => selectedWeekStart.value === workbenchMyWeekWorklogMock.current.weekStart);
|
||||
// ============ 我的工时 ============
|
||||
|
||||
const totalLabel = computed(() => (isCurrentWeek.value ? '累计' : '上周累计'));
|
||||
const myView = computed(() => (myWeekData.value ? buildWorkbenchWeekWorklogView(myWeekData.value) : null));
|
||||
|
||||
const isCurrentWeek = computed(() => selectedWeekStart.value === dayjs().startOf('isoWeek').format('YYYY-MM-DD'));
|
||||
|
||||
const totalLabel = computed(() => (isCurrentWeek.value ? '累计' : '该周累计'));
|
||||
|
||||
const deltaInfo = computed(() => {
|
||||
if (!myView.value) return null;
|
||||
@@ -80,9 +121,7 @@ const deltaInfo = computed(() => {
|
||||
return { text: `达成 ${completionRate}%`, tone: completionRate >= 100 ? ('success' as const) : ('muted' as const) };
|
||||
});
|
||||
|
||||
// 每日柱图的"按天/按周"分色(与项目色无关,保留本地常量)
|
||||
const DAY_BAR_COLOR = '#409EFF';
|
||||
const WEEK_BAR_COLOR = '#A0CFFF';
|
||||
|
||||
interface DistributionRow extends WorkbenchWorklogDistributionItem {
|
||||
color: string;
|
||||
@@ -155,10 +194,8 @@ function buildMyBarOption(): ECOption {
|
||||
formatter: (rawParams: any) => {
|
||||
const params = Array.isArray(rawParams) ? rawParams : [rawParams];
|
||||
const dayName = params[0]?.axisValue ?? '';
|
||||
const dayPart = params.find((p: any) => p.seriesName === '按天填')?.value ?? 0;
|
||||
const weekPart = params.find((p: any) => p.seriesName === '按周均分')?.value ?? 0;
|
||||
const total = Number(dayPart) + Number(weekPart);
|
||||
return `周${dayName}:${total}h<br/>按天填 ${dayPart}h<br/>按周均分 ${weekPart}h`;
|
||||
const total = Number(params[0]?.value ?? 0);
|
||||
return `周${dayName}:${total}h`;
|
||||
}
|
||||
},
|
||||
grid: { left: 28, right: 8, top: 16, bottom: 24, containLabel: false },
|
||||
@@ -176,20 +213,11 @@ function buildMyBarOption(): ECOption {
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '按天填',
|
||||
name: '每日工时',
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
barWidth: 18,
|
||||
data: v?.dailyByDay ?? [],
|
||||
itemStyle: { color: DAY_BAR_COLOR, borderRadius: [0, 0, 2, 2] }
|
||||
},
|
||||
{
|
||||
name: '按周均分',
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
barWidth: 18,
|
||||
data: v?.dailyByWeekAvg ?? [],
|
||||
itemStyle: { color: WEEK_BAR_COLOR, borderRadius: [2, 2, 0, 0] }
|
||||
data: v?.dailyHours ?? [],
|
||||
itemStyle: { color: DAY_BAR_COLOR, borderRadius: [2, 2, 0, 0] }
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -208,16 +236,7 @@ const { domRef: myBarRef, updateOptions: updateMyBar } = useEcharts(buildMyBarOp
|
||||
|
||||
// ============ 团队工时 ============
|
||||
|
||||
const teamView = computed<WorkbenchTeamWorklogView | null>(() => {
|
||||
if (!selectedWeekStart.value) return null;
|
||||
if (selectedWeekStart.value === workbenchTeamWorklogMock.current.weekStart) {
|
||||
return buildWorkbenchTeamWorklogView(workbenchTeamWorklogMock.current);
|
||||
}
|
||||
if (selectedWeekStart.value === workbenchTeamWorklogMock.previous.weekStart) {
|
||||
return buildWorkbenchTeamWorklogView(workbenchTeamWorklogMock.previous);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const teamView = computed(() => (teamWeekData.value ? buildWorkbenchTeamWorklogView(teamWeekData.value) : null));
|
||||
|
||||
const teamSeriesWithColor = computed(() =>
|
||||
(teamView.value?.seriesMatrix ?? []).map(s => ({ ...s, color: getWorkbenchItemColor(s.key, s.kind) }))
|
||||
@@ -339,6 +358,9 @@ watch(activeTab, async tab => {
|
||||
<div class="ww-section-title">
|
||||
<SvgIcon icon="mdi:calendar-week" class="ww-section-icon" />
|
||||
<span>每日工时</span>
|
||||
<ElTooltip content="系统按填报日期段均摊到工作日的推算值(周末份额计入周五),非逐日实填" placement="top">
|
||||
<SvgIcon icon="mdi:information-outline" class="ww-section-info" />
|
||||
</ElTooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -351,16 +373,6 @@ watch(activeTab, async tab => {
|
||||
|
||||
<div class="ww-block">
|
||||
<div ref="myBarRef" class="ww-bar" />
|
||||
<div class="ww-bar-legend">
|
||||
<span class="ww-bar-legend__item">
|
||||
<span class="ww-bar-legend__swatch" :style="{ background: DAY_BAR_COLOR }" />
|
||||
按天填
|
||||
</span>
|
||||
<span class="ww-bar-legend__item">
|
||||
<span class="ww-bar-legend__swatch" :style="{ background: WEEK_BAR_COLOR }" />
|
||||
按周均分
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -410,7 +422,7 @@ watch(activeTab, async tab => {
|
||||
{{ teamView.highCount }}
|
||||
<span class="tw-kpi__unit">人</span>
|
||||
</span>
|
||||
<span class="tw-kpi__sub">超 45h</span>
|
||||
<span class="tw-kpi__sub">超 {{ teamView.overtimeThreshold }}h</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -523,6 +535,12 @@ watch(activeTab, async tab => {
|
||||
color: var(--el-text-color-placeholder);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.ww-section-info {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
flex-shrink: 0;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.ww-pie-wrap {
|
||||
position: relative;
|
||||
@@ -540,26 +558,6 @@ watch(activeTab, async tab => {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.ww-bar-legend {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
margin-top: 8px;
|
||||
font-size: 11px;
|
||||
color: var(--el-text-color-secondary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.ww-bar-legend__item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.ww-bar-legend__swatch {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.ww-footer {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user