🔖 项目重命名.
This commit is contained in:
22
orion-visor-ui/src/components/system/message-box/const.ts
Normal file
22
orion-visor-ui/src/components/system/message-box/const.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
// 消息状态
|
||||
export const MessageStatus = {
|
||||
UNREAD: 0,
|
||||
READ: 1,
|
||||
};
|
||||
|
||||
export const MESSAGE_CONFIG_KEY = 'messageConfig';
|
||||
|
||||
// 查询数量
|
||||
export const messageLimit = 15;
|
||||
|
||||
// 默认消息分类 通知
|
||||
export const defaultClassify = 'NOTICE';
|
||||
|
||||
// 消息分类 字典项
|
||||
export const messageClassifyKey = 'messageClassify';
|
||||
|
||||
// 消息类型 字典项
|
||||
export const messageTypeKey = 'messageType';
|
||||
|
||||
// 加载的字典值
|
||||
export const dictKeys = [messageClassifyKey, messageTypeKey];
|
||||
266
orion-visor-ui/src/components/system/message-box/index.vue
Normal file
266
orion-visor-ui/src/components/system/message-box/index.vue
Normal file
@@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<div class="full">
|
||||
<!-- 消息分类 -->
|
||||
<a-spin class="message-classify-container"
|
||||
:hide-icon="true"
|
||||
:loading="fetchLoading">
|
||||
<a-tabs v-model:activeKey="currentClassify"
|
||||
type="rounded"
|
||||
:hide-content="true"
|
||||
@change="loadClassifyMessage">
|
||||
<!-- 消息列表 -->
|
||||
<a-tab-pane v-for="item in toOptions(messageClassifyKey)"
|
||||
:key="item.value as string">
|
||||
<!-- 标题 -->
|
||||
<template #title>
|
||||
<span class="usn">{{ item.label }} ({{ classifyCount[item.value as any] || 0 }})</span>
|
||||
</template>
|
||||
<!-- 消息列表 -->
|
||||
</a-tab-pane>
|
||||
<!-- 右侧操作 -->
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<!-- 状态 -->
|
||||
<a-switch v-model="queryUnread"
|
||||
type="round"
|
||||
checked-text="未读"
|
||||
unchecked-text="全部"
|
||||
@change="reloadAllMessage" />
|
||||
<!-- 清空 -->
|
||||
<a-button class="header-button"
|
||||
type="text"
|
||||
size="small"
|
||||
@click="clearAllMessage">
|
||||
清空
|
||||
</a-button>
|
||||
<!-- 全部已读 -->
|
||||
<a-button class="header-button"
|
||||
type="text"
|
||||
size="small"
|
||||
@click="setAllRead">
|
||||
全部已读
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-tabs>
|
||||
</a-spin>
|
||||
<!-- 消息列表 -->
|
||||
<list :fetch-loading="fetchLoading"
|
||||
:message-loading="messageLoading"
|
||||
:has-more="hasMore"
|
||||
:message-list="messageList"
|
||||
@load="loadMessage"
|
||||
@click="clickMessage"
|
||||
@delete="deleteMessage" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'messageBox'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { MessageRecordResponse } from '@/api/system/message';
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import {
|
||||
clearSystemMessage,
|
||||
deleteSystemMessage,
|
||||
getSystemMessageCount,
|
||||
getSystemMessageList,
|
||||
updateSystemMessageRead,
|
||||
updateSystemMessageReadAll
|
||||
} from '@/api/system/message';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { clearHtmlTag, replaceHtmlTag } from '@/utils';
|
||||
import { useDictStore } from '@/store';
|
||||
import { dictKeys, messageClassifyKey, messageTypeKey, defaultClassify, MESSAGE_CONFIG_KEY, messageLimit, MessageStatus } from './const';
|
||||
import List from './list.vue';
|
||||
|
||||
const { loading: fetchLoading, setLoading: setFetchLoading } = useLoading();
|
||||
const { loading: messageLoading, setLoading: setMessageLoading } = useLoading();
|
||||
const { loadKeys, toOptions, getDictValue } = useDictStore();
|
||||
const router = useRouter();
|
||||
|
||||
const currentClassify = ref(defaultClassify);
|
||||
const queryUnread = ref(false);
|
||||
const classifyCount = ref<Record<string, number>>({});
|
||||
const messageList = ref<Array<MessageRecordResponse>>([]);
|
||||
const hasMore = ref(true);
|
||||
|
||||
// 重新加载消息
|
||||
const reloadAllMessage = async () => {
|
||||
hasMore.value = true;
|
||||
messageList.value = [];
|
||||
// 查询数量
|
||||
queryMessageCount();
|
||||
// 加载列表
|
||||
await loadMessage();
|
||||
};
|
||||
|
||||
// 获取数量
|
||||
const queryMessageCount = async () => {
|
||||
setFetchLoading(true);
|
||||
try {
|
||||
const { data } = await getSystemMessageCount(queryUnread.value);
|
||||
classifyCount.value = data;
|
||||
} catch (ex) {
|
||||
} finally {
|
||||
setFetchLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 查询分类消息
|
||||
const loadClassifyMessage = async () => {
|
||||
hasMore.value = true;
|
||||
messageList.value = [];
|
||||
await loadMessage();
|
||||
};
|
||||
|
||||
// 加载消息
|
||||
const loadMessage = async () => {
|
||||
hasMore.value = true;
|
||||
setFetchLoading(true);
|
||||
try {
|
||||
const maxId = messageList.value.length
|
||||
? messageList.value[messageList.value.length - 1].id
|
||||
: undefined;
|
||||
// 查询数据
|
||||
const { data } = await getSystemMessageList({
|
||||
limit: messageLimit,
|
||||
classify: currentClassify.value,
|
||||
queryUnread: queryUnread.value,
|
||||
maxId,
|
||||
});
|
||||
data.forEach(s => {
|
||||
messageList.value.push({
|
||||
...s,
|
||||
content: clearHtmlTag(s.content),
|
||||
contentHtml: replaceHtmlTag(s.content),
|
||||
});
|
||||
});
|
||||
hasMore.value = data.length === messageLimit;
|
||||
} catch (ex) {
|
||||
} finally {
|
||||
setFetchLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 设置全部已读
|
||||
const setAllRead = async () => {
|
||||
setMessageLoading(true);
|
||||
try {
|
||||
// 设置为已读
|
||||
await updateSystemMessageReadAll(currentClassify.value);
|
||||
// 修改状态
|
||||
messageList.value.forEach(s => s.status = MessageStatus.READ);
|
||||
} catch (ex) {
|
||||
} finally {
|
||||
setMessageLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 清理已读消息
|
||||
const clearAllMessage = async () => {
|
||||
setMessageLoading(true);
|
||||
try {
|
||||
// 清理消息
|
||||
await clearSystemMessage(currentClassify.value);
|
||||
} catch (ex) {
|
||||
} finally {
|
||||
setMessageLoading(false);
|
||||
}
|
||||
// 查询消息
|
||||
await reloadAllMessage();
|
||||
};
|
||||
|
||||
// 点击消息
|
||||
const clickMessage = (message: MessageRecordResponse) => {
|
||||
// 设置为已读
|
||||
if (message.status === MessageStatus.UNREAD) {
|
||||
updateSystemMessageRead(message.id);
|
||||
message.status = MessageStatus.READ;
|
||||
}
|
||||
const redirectComponent = getDictValue(messageTypeKey, message.type, 'redirectComponent');
|
||||
if (redirectComponent && redirectComponent !== '0') {
|
||||
// 跳转组件
|
||||
router.push({ name: redirectComponent, query: { key: message.relKey } });
|
||||
}
|
||||
};
|
||||
|
||||
// 删除消息
|
||||
const deleteMessage = async (message: MessageRecordResponse) => {
|
||||
setMessageLoading(true);
|
||||
try {
|
||||
// 删除消息
|
||||
await deleteSystemMessage(message.id);
|
||||
// 减少数量
|
||||
classifyCount.value[currentClassify.value] -= 1;
|
||||
// 移除
|
||||
const index = messageList.value.findIndex(s => s.id === message.id);
|
||||
messageList.value.splice(index, 1);
|
||||
} catch (ex) {
|
||||
} finally {
|
||||
setMessageLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载字典值
|
||||
onMounted(() => {
|
||||
loadKeys(dictKeys);
|
||||
});
|
||||
|
||||
// 获取消息
|
||||
onMounted(() => {
|
||||
// 获取配置缓存
|
||||
const item = localStorage.getItem(MESSAGE_CONFIG_KEY);
|
||||
if (item) {
|
||||
const config = JSON.parse(item) as Record<string, any>;
|
||||
if (config?.currentClassify) {
|
||||
currentClassify.value = config.currentClassify;
|
||||
}
|
||||
if (config?.queryUnread) {
|
||||
queryUnread.value = config.queryUnread;
|
||||
}
|
||||
}
|
||||
// 查询数据
|
||||
reloadAllMessage();
|
||||
});
|
||||
|
||||
// 设置缓存配置
|
||||
onUnmounted(() => {
|
||||
localStorage.setItem(MESSAGE_CONFIG_KEY, JSON.stringify({
|
||||
currentClassify: currentClassify.value,
|
||||
queryUnread: queryUnread.value,
|
||||
}));
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-popover-popup-content) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-nav) {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--color-neutral-3);
|
||||
}
|
||||
|
||||
:deep(.arco-tabs-content) {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.message-classify-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
|
||||
.header-button {
|
||||
padding: 0 6px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
218
orion-visor-ui/src/components/system/message-box/list.vue
Normal file
218
orion-visor-ui/src/components/system/message-box/list.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<!-- 消息列表 -->
|
||||
<a-spin class="message-list-container" :loading="messageLoading">
|
||||
<!-- 加载中 -->
|
||||
<div v-if="!messageList.length && fetchLoading">
|
||||
<!-- 加载中 -->
|
||||
<a-skeleton class="skeleton-wrapper" :animation="true">
|
||||
<a-skeleton-line :rows="3"
|
||||
:line-height="96"
|
||||
:line-spacing="8" />
|
||||
</a-skeleton>
|
||||
</div>
|
||||
<!-- 无数据 -->
|
||||
<div v-else-if="!messageList.length && !fetchLoading">
|
||||
<a-result status="404">
|
||||
<template #subtitle>暂无内容</template>
|
||||
</a-result>
|
||||
</div>
|
||||
<!-- 消息容器 -->
|
||||
<div v-else class="message-list-wrapper">
|
||||
<a-scrollbar style="overflow-y: auto; height: 100%;">
|
||||
<!-- 消息列表-->
|
||||
<div v-for="message in messageList"
|
||||
class="message-item"
|
||||
:class="[ message.status === MessageStatus.READ ? 'message-item-read' : 'message-item-unread' ]"
|
||||
@click="emits('click', message)">
|
||||
<!-- 标题 -->
|
||||
<div class="message-item-title">
|
||||
<!-- 标题 -->
|
||||
<div class="message-item-title-text text-ellipsis" :title="message.title">
|
||||
{{ message.title }}
|
||||
</div>
|
||||
<!-- tag -->
|
||||
<div class="message-item-title-status">
|
||||
<template v-if="getDictValue(messageTypeKey, message.type, 'tagVisible', false)">
|
||||
<a-tag size="small" :color="getDictValue(messageTypeKey, message.type, 'tagColor')">
|
||||
{{ getDictValue(messageTypeKey, message.type, 'tagLabel') }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 操作 -->
|
||||
<div class="message-item-title-actions">
|
||||
<!-- 删除 -->
|
||||
<a-button size="mini"
|
||||
type="text"
|
||||
status="danger"
|
||||
@click.stop="emits('delete', message)">
|
||||
删除
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 内容 -->
|
||||
<div v-html="message.contentHtml" class="message-item-content" />
|
||||
<!-- 时间 -->
|
||||
<div class="message-item-time">
|
||||
{{ dateFormat(new Date(message.createTime)) }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 加载中 -->
|
||||
<a-skeleton v-if="fetchLoading"
|
||||
class="skeleton-wrapper"
|
||||
:animation="true">
|
||||
<a-skeleton-line :rows="3"
|
||||
:line-height="96"
|
||||
:line-spacing="8" />
|
||||
</a-skeleton>
|
||||
<!-- 加载更多 -->
|
||||
<div v-if="hasMore" class="load-more-wrapper">
|
||||
<a-button size="small"
|
||||
:fetchLoading="fetchLoading"
|
||||
@click="() => emits('load')">
|
||||
加载更多
|
||||
</a-button>
|
||||
</div>
|
||||
</a-scrollbar>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'messageBoxList'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { MessageRecordResponse } from '@/api/system/message';
|
||||
import { MessageStatus, messageTypeKey } from './const';
|
||||
import { useDictStore } from '@/store';
|
||||
import { dateFormat } from '@/utils';
|
||||
|
||||
const emits = defineEmits(['load', 'click', 'delete']);
|
||||
const props = defineProps<{
|
||||
fetchLoading: boolean;
|
||||
messageLoading: boolean;
|
||||
hasMore: boolean;
|
||||
messageList: Array<MessageRecordResponse>;
|
||||
}>();
|
||||
|
||||
const { getDictValue } = useDictStore();
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@gap: 8px;
|
||||
@actions-width: 82px;
|
||||
|
||||
.skeleton-wrapper {
|
||||
padding: 8px 12px 0 12px;
|
||||
}
|
||||
|
||||
.message-list-container {
|
||||
width: 100%;
|
||||
height: 338px;
|
||||
display: block;
|
||||
|
||||
.message-list-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.load-more-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 12px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.message-item {
|
||||
padding: 12px 20px;
|
||||
border-bottom: 1px solid var(--color-neutral-3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all .2s;
|
||||
|
||||
&-title {
|
||||
height: 22px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
|
||||
&-text {
|
||||
width: calc(100% - @actions-width - @gap);
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
text-overflow: clip;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
&-status {
|
||||
width: @actions-width;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&-actions {
|
||||
width: @actions-width;
|
||||
display: none;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
|
||||
button {
|
||||
padding: 0 6px !important;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-fill-3) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
|
||||
&-time {
|
||||
height: 18px;
|
||||
margin-top: 4px;
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
}
|
||||
|
||||
.message-item:hover {
|
||||
background: var(--color-fill-1);
|
||||
|
||||
.message-item-title-status {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.message-item-title-actions {
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.message-item-read {
|
||||
.message-item-title-text, .message-item-title-status, .message-item-content, .message-item-time {
|
||||
opacity: .65;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-scrollbar) {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user