Files
cn-rdms-web/src/views/personal-center/my-performance/modules/performance-summary.vue

323 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { computed, ref } from 'vue';
import { remindTeamPerformance } from '@/service/api';
import { formatDateTime, formatScore, getDeptOrgTypeLabel, getPerformanceStatusLabel } from './performance-shared';
defineOptions({ name: 'PerformanceSummary' });
interface Props {
periodMonthStart: string;
periodMonthEnd: string;
loading?: boolean;
summary?: Api.Performance.Team.Summary | null;
}
const props = withDefaults(defineProps<Props>(), {
loading: false,
summary: null
});
const emit = defineEmits<{
reminded: [];
}>();
const remindingKey = ref('');
const deptOrgAverageCount = computed(() => props.summary?.deptOrgAverages?.length ?? 0);
const periodLabel = computed(() => {
const start = props.periodMonthStart;
const end = props.periodMonthEnd;
if (!start) return '';
if (!end || start === end) return start;
return `${start}${end}`;
});
const cards = computed(() => [
{ label: '绩效表总数', value: props.summary?.totalSheetCount ?? 0 },
{ label: '待发送数', value: props.summary?.pendingSendCount ?? 0, key: 'pending_send' as const },
{ label: '待确认数', value: props.summary?.pendingConfirmCount ?? 0, key: 'pending_confirm' as const },
{ label: '已确认率', value: `${props.summary?.confirmedRate ?? '0.00'}%` },
{ label: '各方向绩效平均分', value: deptOrgAverageCount.value, key: 'dept_org_average' as const }
]);
async function handleRemind(type: Api.Performance.Common.RemindType, userIds?: string[]) {
const key = userIds?.length === 1 ? `${type}:${userIds[0]}` : `${type}:all`;
remindingKey.value = key;
const { error, data } = await remindTeamPerformance({
periodMonthStart: props.periodMonthStart,
periodMonthEnd: props.periodMonthEnd,
remindType: type,
userIds
});
remindingKey.value = '';
if (error) return;
window.$message?.success(`已催办 ${data?.remindedCount ?? 0}`);
emit('reminded');
}
</script>
<template>
<div v-loading="props.loading" class="performance-summary">
<div v-if="periodLabel" class="performance-summary__period">{{ periodLabel }}</div>
<div class="performance-summary__grid">
<div v-for="card in cards" :key="card.label" class="performance-summary__item">
<div class="performance-summary__label">{{ card.label }}</div>
<div class="performance-summary__value">
<template v-if="card.key === 'pending_send'">
<ElPopover placement="bottom" :width="360" trigger="hover">
<template #reference>
<button type="button" class="performance-summary__link-button">{{ card.value }}</button>
</template>
<div class="performance-summary__popover">
<div class="performance-summary__popover-title">待发送人员</div>
<div v-if="props.summary?.pendingSendUsers?.length" class="performance-summary__user-list">
<div
v-for="user in props.summary.pendingSendUsers"
:key="`${user.userId}-${user.sheetId || 'none'}`"
class="performance-summary__user-item"
>
<div class="performance-summary__user-main">
<span>{{ user.userNickname }}</span>
<small>
{{ getPerformanceStatusLabel(user.statusCode) }}提醒 {{ user.managerName || '直属上级' }}
</small>
</div>
<ElButton
link
type="primary"
:loading="remindingKey === `pending_send:${user.userId}`"
@click="handleRemind('pending_send', [user.userId])"
>
催办
</ElButton>
</div>
</div>
<ElEmpty v-else :image-size="60" description="暂无待发送人员" />
<div class="performance-summary__popover-footer">
<ElButton
size="small"
type="primary"
plain
:loading="remindingKey === 'pending_send:all'"
:disabled="!props.summary?.pendingSendUsers?.length"
@click="handleRemind('pending_send')"
>
一键催办全部
</ElButton>
</div>
</div>
</ElPopover>
</template>
<template v-else-if="card.key === 'pending_confirm'">
<ElPopover placement="bottom" :width="340" trigger="hover">
<template #reference>
<button type="button" class="performance-summary__link-button">{{ card.value }}</button>
</template>
<div class="performance-summary__popover">
<div class="performance-summary__popover-title">待确认人员</div>
<div v-if="props.summary?.pendingConfirmUsers?.length" class="performance-summary__user-list">
<div
v-for="user in props.summary.pendingConfirmUsers"
:key="user.sheetId"
class="performance-summary__user-item"
>
<div class="performance-summary__user-main">
<span>{{ user.userNickname }}</span>
<small>发送时间{{ formatDateTime(user.sentTime) }}</small>
</div>
<ElButton
link
type="primary"
:loading="remindingKey === `pending_confirm:${user.userId}`"
@click="handleRemind('pending_confirm', [user.userId])"
>
催办
</ElButton>
</div>
</div>
<ElEmpty v-else :image-size="60" description="暂无待确认人员" />
<div class="performance-summary__popover-footer">
<ElButton
size="small"
type="primary"
plain
:loading="remindingKey === 'pending_confirm:all'"
:disabled="!props.summary?.pendingConfirmUsers?.length"
@click="handleRemind('pending_confirm')"
>
一键催办全部
</ElButton>
</div>
</div>
</ElPopover>
</template>
<template v-else-if="card.key === 'dept_org_average'">
<ElPopover placement="bottom" :width="360" trigger="hover">
<template #reference>
<button type="button" class="performance-summary__link-button">{{ card.value }}</button>
</template>
<div class="performance-summary__popover">
<div class="performance-summary__popover-title">各方向绩效平均分</div>
<div v-if="props.summary?.deptOrgAverages?.length" class="performance-summary__user-list">
<div
v-for="item in props.summary.deptOrgAverages"
:key="item.deptId"
class="performance-summary__user-item performance-summary__user-item--score"
>
<div class="performance-summary__user-main">
<span>{{ item.deptName }}</span>
<small>{{ getDeptOrgTypeLabel(item.deptOrgType) }} / {{ item.confirmedCount }} </small>
</div>
<strong class="performance-summary__score">{{ formatScore(item.averageScore) }}</strong>
</div>
</div>
<ElEmpty v-else :image-size="60" description="暂无方向平均分" />
</div>
</ElPopover>
</template>
<template v-else>
{{ card.value }}
</template>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.performance-summary {
display: grid;
gap: 12px;
}
.performance-summary__period {
color: var(--el-text-color-secondary);
font-size: 12px;
}
.performance-summary__grid {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 12px;
}
.performance-summary__item {
display: grid;
gap: 8px;
padding: 14px 16px;
border: 1px solid var(--el-border-color-light);
border-radius: 8px;
background: var(--el-fill-color-blank);
}
.performance-summary__label {
color: var(--el-text-color-secondary);
font-size: 12px;
}
.performance-summary__value {
color: var(--el-text-color-primary);
font-size: 22px;
font-weight: 600;
line-height: 1.2;
}
.performance-summary__link-button {
padding: 0;
border: none;
background: transparent;
color: var(--el-color-primary);
font: inherit;
cursor: pointer;
}
.performance-summary__popover {
display: grid;
gap: 10px;
}
.performance-summary__popover-title {
color: var(--el-text-color-primary);
font-size: 14px;
font-weight: 600;
}
.performance-summary__user-list {
display: grid;
gap: 8px;
max-height: 260px;
overflow: auto;
}
.performance-summary__user-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 8px 10px;
border-radius: 8px;
background: var(--el-fill-color-light);
}
.performance-summary__user-item--score {
align-items: flex-start;
}
.performance-summary__user-main {
display: grid;
gap: 3px;
min-width: 0;
}
.performance-summary__user-main span {
color: var(--el-text-color-regular);
}
.performance-summary__user-main small {
color: var(--el-text-color-secondary);
font-size: 12px;
}
.performance-summary__popover-footer {
display: flex;
justify-content: flex-end;
}
.performance-summary__score {
color: var(--el-color-primary);
font-size: 16px;
line-height: 1.5;
}
@media (width <= 1400px) {
.performance-summary__grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (width <= 900px) {
.performance-summary__grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (width <= 640px) {
.performance-summary__grid {
grid-template-columns: 1fr;
}
}
</style>