Files
cn-rdms-web/src/views/workbench/modules/workbench-team-load.vue

284 lines
7.0 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { computed } from 'vue';
import { getWorkbenchItemColor } from '../composables/use-workbench-colors';
import { type WorkbenchTeamLoadLevel, buildWorkbenchTeamLoadView } from '../homepage';
import { workbenchTeamLoadMock } from '../mock';
import { useWorkbenchRefresh } from '../composables/use-workbench-refresh';
import WorkbenchModuleCard from './workbench-module-card.vue';
defineOptions({ name: 'WorkbenchTeamLoad' });
interface Props {
editing?: boolean;
}
withDefaults(defineProps<Props>(), { editing: false });
defineEmits<{ (e: 'hide'): void }>();
const { loading, refresh } = useWorkbenchRefresh();
const view = computed(() => buildWorkbenchTeamLoadView(workbenchTeamLoadMock));
const LEVEL_LABEL: Record<WorkbenchTeamLoadLevel, string> = {
high: '高负载',
mid: '中负载',
normal: '正常'
};
function urgentTooltip(dueSoon: number, overdue: number) {
if (dueSoon > 0 && overdue > 0) return `临期 ${dueSoon} · 逾期 ${overdue}`;
if (overdue > 0) return `逾期 ${overdue}`;
return `临期 ${dueSoon}`;
}
</script>
<template>
<WorkbenchModuleCard
v-loading="loading"
title="团队负载"
icon="mdi:scale-balance"
:editing="editing"
@hide="$emit('hide')"
@refresh="refresh"
>
<div class="tl-kpis">
<div class="tl-kpi">
<span class="tl-kpi__label">高负载</span>
<span class="tl-kpi__value" :class="{ 'is-danger': view.highCount > 0 }">
{{ view.highCount }}
<span class="tl-kpi__unit"></span>
</span>
</div>
<div class="tl-kpi">
<span class="tl-kpi__label">中负载</span>
<span class="tl-kpi__value" :class="{ 'is-warn': view.midCount > 0 }">
{{ view.midCount }}
<span class="tl-kpi__unit"></span>
</span>
</div>
<div class="tl-kpi">
<span class="tl-kpi__label">临期 + 逾期</span>
<span class="tl-kpi__value" :class="{ 'is-danger': view.urgentTotal > 0 }">
{{ view.urgentTotal }}
<span class="tl-kpi__unit"></span>
</span>
</div>
</div>
<ul class="tl-list">
<li v-for="m in view.members" :key="m.memberId" class="tl-row">
<span class="tl-row__dot" :class="`is-${m.level}`" :title="LEVEL_LABEL[m.level]" />
<span class="tl-row__name">{{ m.memberName }}</span>
<div class="tl-row__bar-wrap">
<div class="tl-row__bar" :style="{ width: `${m.barWidthPercent}%` }">
<ElTooltip
v-for="seg in m.segments"
:key="seg.key"
:content="`${seg.label} · ${seg.count} 个`"
placement="top"
>
<div
class="tl-row__seg"
:style="{
width: `${seg.widthPercent}%`,
background: getWorkbenchItemColor(seg.key, seg.kind)
}"
/>
</ElTooltip>
</div>
<span v-if="m.overflowExtra > 0" class="tl-row__overflow">+{{ m.overflowExtra }}</span>
</div>
<span class="tl-row__metrics">
<span class="tl-row__metric" :class="`is-${m.level}`">
<b>{{ m.inProgress }}</b>
进行
</span>
<span v-if="m.urgent > 0" class="tl-row__metric is-urgent">
<ElTooltip :content="urgentTooltip(m.dueSoon, m.overdue)" placement="top">
<span>
<b>{{ m.urgent }}</b>
临期
<SvgIcon v-if="m.overdue > 0" icon="mdi:alert" class="tl-row__warn-icon" />
</span>
</ElTooltip>
</span>
</span>
</li>
</ul>
<div class="tl-hint"> = 进行中 6 临期+逾期 2 · = 进行中 4 临期+逾期 1</div>
</WorkbenchModuleCard>
</template>
<style scoped>
.tl-kpis {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
margin-bottom: 12px;
}
.tl-kpi {
display: flex;
flex-direction: column;
gap: 2px;
padding: 10px 12px;
background: var(--el-fill-color-lighter);
border-radius: 8px;
min-width: 0;
}
.tl-kpi__label {
font-size: 11px;
color: var(--el-text-color-secondary);
}
.tl-kpi__value {
font-size: 20px;
font-weight: 700;
color: var(--el-text-color-primary);
line-height: 1.2;
}
.tl-kpi__value.is-danger {
color: var(--el-color-danger);
}
.tl-kpi__value.is-warn {
color: var(--el-color-warning);
}
.tl-kpi__unit {
font-size: 12px;
font-weight: 500;
color: var(--el-text-color-secondary);
margin-left: 2px;
}
.tl-list {
list-style: none;
margin: 0;
padding: 0;
flex: 1;
min-height: 0;
overflow: auto;
}
.tl-list::-webkit-scrollbar {
width: 6px;
}
.tl-list::-webkit-scrollbar-thumb {
background: var(--el-fill-color-darker);
border-radius: 3px;
}
.tl-list::-webkit-scrollbar-thumb:hover {
background: var(--el-border-color);
}
.tl-row {
display: grid;
grid-template-columns: 10px 64px 1fr auto;
align-items: center;
gap: 10px;
padding: 7px 0;
font-size: 13px;
border-bottom: 1px dashed var(--el-border-color-lighter);
}
.tl-row:last-child {
border-bottom: none;
}
.tl-row__dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--el-color-success);
}
.tl-row__dot.is-high {
background: var(--el-color-danger);
}
.tl-row__dot.is-mid {
background: var(--el-color-warning);
}
.tl-row__dot.is-normal {
background: var(--el-color-success);
}
.tl-row__name {
color: var(--el-text-color-primary);
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tl-row__bar-wrap {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
.tl-row__bar {
height: 8px;
border-radius: 4px;
background: var(--el-fill-color);
overflow: hidden;
display: flex;
min-width: 0;
transition: width 0.3s ease;
}
.tl-row__seg {
height: 100%;
transition: filter 0.15s ease;
cursor: default;
}
.tl-row__seg:hover {
filter: brightness(0.92);
}
.tl-row__seg + .tl-row__seg {
box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.75);
}
.tl-row__overflow {
flex-shrink: 0;
display: inline-flex;
align-items: center;
padding: 1px 6px;
border-radius: 8px;
background: var(--el-color-danger);
color: #fff;
font-size: 10px;
font-weight: 600;
line-height: 1.4;
}
.tl-row__metrics {
display: inline-flex;
align-items: center;
gap: 10px;
font-size: 12px;
color: var(--el-text-color-secondary);
white-space: nowrap;
}
.tl-row__metric b {
color: var(--el-text-color-primary);
font-weight: 600;
margin-right: 2px;
}
.tl-row__metric.is-high b {
color: var(--el-color-danger);
}
.tl-row__metric.is-mid b {
color: var(--el-color-warning);
}
.tl-row__metric.is-normal b {
color: var(--el-color-success);
}
.tl-row__metric.is-urgent {
color: var(--el-color-danger);
}
.tl-row__metric.is-urgent b {
color: var(--el-color-danger);
}
.tl-row__warn-icon {
vertical-align: -2px;
margin-left: 2px;
font-size: 12px;
color: var(--el-color-danger);
}
.tl-hint {
margin-top: 10px;
font-size: 11px;
color: var(--el-text-color-placeholder);
line-height: 1.5;
}
</style>