Files
cn-rdms-web/src/views/personal-center/my-performance/modules/performance-summary.vue
dk 9a5845708d feat(我的绩效): 开发我的绩效功能。
fix(加班申请、工作报告): 重构加班申请在审批时的样式,工作报告在新增时的对话框、报告详情页的样式。
2026-06-21 18:22:44 +08:00

309 lines
9.7 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 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 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__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>