feat(performance、notify、weekly): 添加绩效考核功能、通知跳转链接、优化工作日志分割逻辑。

This commit is contained in:
dk
2026-07-07 16:51:34 +08:00
parent c1f710fbd8
commit a0dcd39c23
6 changed files with 330 additions and 34 deletions

View File

@@ -1,6 +1,8 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { useDebounceFn, useInfiniteScroll } from '@vueuse/core';
import { OBJECT_CONTEXT_QUERY_KEY } from '@/constants/object-context';
import { NOTIFY_MESSAGE_LEVEL_DICT_CODE } from '@/constants/dict';
import {
fetchGetMyNotifyMessagePage,
@@ -14,6 +16,7 @@ import { formatDateTime, formatRelativeTime } from '@/utils/datetime';
defineOptions({ name: 'NotificationBell' });
const dictStore = useDictStore();
const router = useRouter();
const PAGE_SIZE = 10;
const UNREAD_COUNT_POLL_INTERVAL = 15 * 1000;
@@ -50,6 +53,12 @@ const searchKeyword = ref('');
const detailVisible = ref(false);
const detailMessage = ref<Api.NotifyMessage.NotifyMessage | null>(null);
interface NotifyRenderParts {
prefix: string;
clickable: string;
suffix: string;
}
function keywordParam() {
return searchKeyword.value.trim() || undefined;
}
@@ -202,6 +211,89 @@ function openDetail(row: Api.NotifyMessage.NotifyMessage) {
}
}
function getClickableText(message: Api.NotifyMessage.NotifyMessage) {
const params = message.templateParams;
switch (message.templateCode) {
case 'execution_assigned':
return params?.executionName?.trim() || '';
case 'task_assigned':
return params?.taskName?.trim() || '';
case 'project_created':
return params?.projectName?.trim() || '';
default:
return '';
}
}
function canJump(message: Api.NotifyMessage.NotifyMessage) {
const params = message.templateParams;
return Boolean(params?.jumpType && params.projectId && getClickableText(message));
}
function renderNotifyParts(message: Api.NotifyMessage.NotifyMessage): NotifyRenderParts {
const content = message.templateContent || '';
const clickable = getClickableText(message);
if (!content || !clickable) {
return { prefix: content, clickable: '', suffix: '' };
}
const startIndex = content.indexOf(clickable);
if (startIndex < 0) {
return { prefix: content, clickable: '', suffix: '' };
}
return {
prefix: content.slice(0, startIndex),
clickable,
suffix: content.slice(startIndex + clickable.length)
};
}
async function handleNotifyJump(message: Api.NotifyMessage.NotifyMessage) {
const params = message.templateParams;
if (!params?.jumpType || !params.projectId) return;
const query: Record<string, string> = {
[OBJECT_CONTEXT_QUERY_KEY]: params.projectId
};
let path = '';
switch (params.jumpType) {
case 'project':
path = '/project/project/overview';
break;
case 'execution':
path = '/project/project/execution';
if (params.executionName) {
query.executionName = params.executionName;
}
break;
case 'task':
path = '/project/project/execution';
if (params.taskName) {
query.taskName = params.taskName;
}
break;
default:
return;
}
detailVisible.value = false;
drawerOpen.value = false;
await router.push({ path, query });
}
async function onNotifyLinkClick(row: Api.NotifyMessage.NotifyMessage) {
if (!canJump(row)) return;
if (!row.readStatus) {
await markRead(row);
}
await handleNotifyJump(row);
}
async function markAllRead() {
const { error } = await fetchUpdateAllNotifyMessageRead();
if (error) return;
@@ -281,7 +373,25 @@ onBeforeUnmount(() => {
>
<span class="notification-bell__row-dot" :style="{ backgroundColor: levelDotColor(row.level) }" />
<div class="notification-bell__row-body">
<div class="notification-bell__row-title">{{ row.templateContent }}</div>
<div class="notification-bell__row-title">
<template v-if="canJump(row)">
<template v-for="(part, index) in [renderNotifyParts(row)]" :key="`${row.id}-${index}`">
<span>{{ part.prefix }}</span>
<button
v-if="part.clickable"
type="button"
class="notification-bell__inline-link"
@click.stop="onNotifyLinkClick(row)"
>
{{ part.clickable }}
</button>
<span>{{ part.suffix }}</span>
</template>
</template>
<template v-else>
{{ row.templateContent }}
</template>
</div>
<div class="notification-bell__row-meta">
<DictTag :dict-code="NOTIFY_MESSAGE_LEVEL_DICT_CODE" :value="row.level" size="small" round />
<span class="notification-bell__row-time">{{ formatRelativeTime(row.createTime) }}</span>
@@ -312,7 +422,25 @@ onBeforeUnmount(() => {
>
<span class="notification-bell__row-dot" :style="{ backgroundColor: levelDotColor(row.level) }" />
<div class="notification-bell__row-body">
<div class="notification-bell__row-title">{{ row.templateContent }}</div>
<div class="notification-bell__row-title">
<template v-if="canJump(row)">
<template v-for="(part, index) in [renderNotifyParts(row)]" :key="`${row.id}-${index}`">
<span>{{ part.prefix }}</span>
<button
v-if="part.clickable"
type="button"
class="notification-bell__inline-link"
@click.stop="onNotifyLinkClick(row)"
>
{{ part.clickable }}
</button>
<span>{{ part.suffix }}</span>
</template>
</template>
<template v-else>
{{ row.templateContent }}
</template>
</div>
<div class="notification-bell__row-meta">
<DictTag :dict-code="NOTIFY_MESSAGE_LEVEL_DICT_CODE" :value="row.level" size="small" round />
<span class="notification-bell__row-time">{{ formatRelativeTime(row.createTime) }}</span>
@@ -350,7 +478,25 @@ onBeforeUnmount(() => {
</div>
</template>
<div v-if="detailMessage" class="notification-bell__detail-body">
<div class="notification-bell__detail-content">{{ detailMessage.templateContent }}</div>
<div class="notification-bell__detail-content">
<template v-if="canJump(detailMessage)">
<template v-for="(part, index) in [renderNotifyParts(detailMessage)]" :key="`detail-${index}`">
<span>{{ part.prefix }}</span>
<button
v-if="part.clickable"
type="button"
class="notification-bell__inline-link"
@click.stop="onNotifyLinkClick(detailMessage)"
>
{{ part.clickable }}
</button>
<span>{{ part.suffix }}</span>
</template>
</template>
<template v-else>
{{ detailMessage.templateContent }}
</template>
</div>
<div class="notification-bell__detail-time">收到于 {{ formatDateTime(detailMessage.createTime) }}</div>
</div>
<template #footer>
@@ -578,6 +724,19 @@ onBeforeUnmount(() => {
font-weight: 500;
}
.notification-bell__inline-link {
padding: 0;
border: 0;
background: transparent;
color: var(--el-color-primary);
cursor: pointer;
font: inherit;
}
.notification-bell__inline-link:hover {
text-decoration: underline;
}
.notification-bell__row-meta {
display: flex;
align-items: center;