|
@@ -1,130 +1,288 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <div v-loading="state.loading" class="layout-navbars-breadcrumb-user-news">
|
|
|
|
|
|
|
+ <div v-loading="loading" class="layout-notice">
|
|
|
<div class="head-box">
|
|
<div class="head-box">
|
|
|
- <div class="head-box-title">通知公告</div>
|
|
|
|
|
- <div class="head-box-btn" @click="readAll">全部已读</div>
|
|
|
|
|
|
|
+ <div class="head-box-title">消息通知</div>
|
|
|
|
|
+ <div class="head-box-btn" @click="handleReadAll">全部已读</div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div v-loading="state.loading" class="content-box">
|
|
|
|
|
- <template v-if="newsList.length > 0">
|
|
|
|
|
- <div v-for="(v, k) in newsList" :key="k" class="content-box-item" @click="onNewsClick(k)">
|
|
|
|
|
- <div class="item-conten">
|
|
|
|
|
- <div>{{ v.message }}</div>
|
|
|
|
|
- <div class="content-box-msg"></div>
|
|
|
|
|
- <div class="content-box-time">{{ v.time }}</div>
|
|
|
|
|
|
|
+ <div class="content-box">
|
|
|
|
|
+ <template v-if="noticeList.length > 0">
|
|
|
|
|
+ <div v-for="item in noticeList" :key="item.id" class="content-box-item" @click="handleDetail(item)">
|
|
|
|
|
+ <div class="item-icon" :style="{ backgroundColor: getIconConfig(item).bgColor }">
|
|
|
|
|
+ <svg-icon :icon-class="getIconConfig(item).icon" />
|
|
|
</div>
|
|
</div>
|
|
|
- <!-- 已读/未读 -->
|
|
|
|
|
- <span v-if="v.read" class="el-tag el-tag--success el-tag--mini read">已读</span>
|
|
|
|
|
- <span v-else class="el-tag el-tag--danger el-tag--mini read">未读</span>
|
|
|
|
|
|
|
+ <div class="item-content">
|
|
|
|
|
+ <div class="item-title text-ellipsis">{{ item.title }}</div>
|
|
|
|
|
+ <div class="item-time">{{ parseTime(item.createTime, '{m}-{d} {h}:{i}') }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-if="!item.readFlag" class="unread-dot"></div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
- <el-empty v-else :description="'消息为空'"></el-empty>
|
|
|
|
|
|
|
+ <el-empty v-else :image-size="60" description="暂无未读消息"></el-empty>
|
|
|
</div>
|
|
</div>
|
|
|
- <div v-if="newsList.length > 0" class="foot-box" @click="onGoToGiteeClick">前往gitee</div>
|
|
|
|
|
|
|
+ <div class="foot-box" @click="handleViewAll">查看全部消息</div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 消息详情对话框 -->
|
|
|
|
|
+ <el-dialog title="消息详情" v-model="detailDialog.open" width="500px" append-to-body destroy-on-close>
|
|
|
|
|
+ <div class="dialog-content-wrapper">
|
|
|
|
|
+ <div class="dialog-header">
|
|
|
|
|
+ <el-tag :type="detailDialog.tagType" effect="plain" size="small">{{ detailDialog.tagLabel }}</el-tag>
|
|
|
|
|
+ <span class="dialog-time">{{ detailDialog.createTime }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="dialog-main-title">{{ detailDialog.title }}</div>
|
|
|
|
|
+ <div class="dialog-desc-block">
|
|
|
|
|
+ {{ detailDialog.content }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="dialog-action-link" @click="handleProcess">
|
|
|
|
|
+ <el-link type="primary" :underline="false">前往处理 >></el-link>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <div class="dialog-footer">
|
|
|
|
|
+ <el-button @click="detailDialog.open = false">关闭</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
-<script setup lang="ts" name="layoutBreadcrumbUserNews">
|
|
|
|
|
-import { useNoticeStore } from '@/store/modules/notice';
|
|
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { listMyNotice, readNotice, readAllNotice } from '@/api/system/notice';
|
|
|
|
|
+import { NoticeVO } from '@/api/system/notice/types';
|
|
|
|
|
+import router from '@/router';
|
|
|
|
|
+
|
|
|
|
|
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
|
|
+const emit = defineEmits(['read']);
|
|
|
|
|
|
|
|
-const noticeStore = useNoticeStore();
|
|
|
|
|
-const { readAll } = useNoticeStore();
|
|
|
|
|
|
|
+const noticeList = ref<NoticeVO[]>([]);
|
|
|
|
|
+const loading = ref(false);
|
|
|
|
|
|
|
|
-// 定义变量内容
|
|
|
|
|
-const state = reactive({
|
|
|
|
|
- loading: false
|
|
|
|
|
|
|
+const detailDialog = reactive({
|
|
|
|
|
+ open: false,
|
|
|
|
|
+ title: '',
|
|
|
|
|
+ content: '',
|
|
|
|
|
+ createTime: '',
|
|
|
|
|
+ tagLabel: '',
|
|
|
|
|
+ tagType: '' as any
|
|
|
});
|
|
});
|
|
|
-const newsList = ref([]) as any;
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * 初始化数据
|
|
|
|
|
- * @returns
|
|
|
|
|
- */
|
|
|
|
|
-const getTableData = async () => {
|
|
|
|
|
- state.loading = true;
|
|
|
|
|
- newsList.value = noticeStore.state.notices;
|
|
|
|
|
- state.loading = false;
|
|
|
|
|
|
|
+
|
|
|
|
|
+/** 获取图标和颜色配置 */
|
|
|
|
|
+const getIconConfig = (item: any) => {
|
|
|
|
|
+ const title = item.title || '';
|
|
|
|
|
+ if (title.includes('异常')) return { icon: 'bug', bgColor: '#f56c6c' };
|
|
|
|
|
+ if (title.includes('拒单')) return { icon: 'message-center', bgColor: '#f56c6c' };
|
|
|
|
|
+ if (title.includes('入驻') || title.includes('申请')) return { icon: 'user', bgColor: '#e6a23c' };
|
|
|
|
|
+ if (title.includes('派单') || title.includes('新订单')) return { icon: 'guide', bgColor: '#409eff' };
|
|
|
|
|
+ if (title.includes('完成')) return { icon: 'finish', bgColor: '#67c23a' };
|
|
|
|
|
+ return { icon: 'message-center', bgColor: '#909399' };
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/** 查询未读消息 */
|
|
|
|
|
+const getList = async () => {
|
|
|
|
|
+ loading.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await listMyNotice({
|
|
|
|
|
+ pageNum: 1,
|
|
|
|
|
+ pageSize: 5,
|
|
|
|
|
+ readFlag: false
|
|
|
|
|
+ } as any);
|
|
|
|
|
+ noticeList.value = res.rows;
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false;
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-//点击消息,写入已读
|
|
|
|
|
-const onNewsClick = (item: any) => {
|
|
|
|
|
- newsList.value[item].read = true;
|
|
|
|
|
- //并且写入pinia
|
|
|
|
|
- noticeStore.state.notices = newsList.value;
|
|
|
|
|
|
|
+/** 消息详情 */
|
|
|
|
|
+const handleDetail = (item: NoticeVO) => {
|
|
|
|
|
+ const title = item.title || '';
|
|
|
|
|
+ detailDialog.title = title;
|
|
|
|
|
+ detailDialog.content = item.content || '';
|
|
|
|
|
+ detailDialog.createTime = proxy?.parseTime(item.createTime, '{y}-{m}-{d} {h}:{i}') || '';
|
|
|
|
|
+
|
|
|
|
|
+ if (title.includes('异常')) {
|
|
|
|
|
+ detailDialog.tagLabel = '异常上报';
|
|
|
|
|
+ detailDialog.tagType = 'danger';
|
|
|
|
|
+ } else if (title.includes('拒单')) {
|
|
|
|
|
+ detailDialog.tagLabel = '拒单通知';
|
|
|
|
|
+ detailDialog.tagType = 'danger';
|
|
|
|
|
+ } else if (title.includes('入驻') || title.includes('申请')) {
|
|
|
|
|
+ detailDialog.tagLabel = '自主注册审核';
|
|
|
|
|
+ detailDialog.tagType = 'warning';
|
|
|
|
|
+ } else if (title.includes('派单') || title.includes('新订单')) {
|
|
|
|
|
+ detailDialog.tagLabel = '新订单';
|
|
|
|
|
+ detailDialog.tagType = 'primary';
|
|
|
|
|
+ } else if (title.includes('完成')) {
|
|
|
|
|
+ detailDialog.tagLabel = '服务完成';
|
|
|
|
|
+ detailDialog.tagType = 'success';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ detailDialog.tagLabel = '系统消息';
|
|
|
|
|
+ detailDialog.tagType = 'info';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ detailDialog.open = true;
|
|
|
|
|
+
|
|
|
|
|
+ if (!item.readFlag) {
|
|
|
|
|
+ readNotice(item.id as number).then(() => {
|
|
|
|
|
+ item.readFlag = true;
|
|
|
|
|
+ emit('read');
|
|
|
|
|
+ setTimeout(getList, 300);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-// 前往通知中心点击
|
|
|
|
|
-const onGoToGiteeClick = () => {
|
|
|
|
|
- window.open('https://gitee.com/dromara/RuoYi-Vue-Plus/tree/5.X/');
|
|
|
|
|
|
|
+/** 前往处理 */
|
|
|
|
|
+const handleProcess = () => {
|
|
|
|
|
+ detailDialog.open = false;
|
|
|
|
|
+ if (detailDialog.title.includes('拒单') || detailDialog.title.includes('派单') || detailDialog.title.includes('异常')) {
|
|
|
|
|
+ router.push({ path: '/order/orderList' });
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-onMounted(() => {
|
|
|
|
|
- nextTick(() => {
|
|
|
|
|
- getTableData();
|
|
|
|
|
|
|
+/** 全部已读 */
|
|
|
|
|
+const handleReadAll = () => {
|
|
|
|
|
+ proxy?.$modal.confirm('是否将全部未读消息标记为已读?').then(() => {
|
|
|
|
|
+ return readAllNotice();
|
|
|
|
|
+ }).then(() => {
|
|
|
|
|
+ proxy?.$modal.msgSuccess('全部已读操作成功');
|
|
|
|
|
+ emit('read');
|
|
|
|
|
+ getList();
|
|
|
});
|
|
});
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/** 查看全部 */
|
|
|
|
|
+const handleViewAll = () => {
|
|
|
|
|
+ router.push('/notice');
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+defineExpose({
|
|
|
|
|
+ getList
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ getList();
|
|
|
});
|
|
});
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
<style lang="scss" scoped>
|
|
|
-.layout-navbars-breadcrumb-user-news {
|
|
|
|
|
|
|
+.layout-notice {
|
|
|
.head-box {
|
|
.head-box {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
|
|
box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
|
color: var(--el-text-color-primary);
|
|
color: var(--el-text-color-primary);
|
|
|
justify-content: space-between;
|
|
justify-content: space-between;
|
|
|
- height: 35px;
|
|
|
|
|
|
|
+ height: 40px;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
|
|
+ padding: 0 16px;
|
|
|
|
|
+ .head-box-title {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ }
|
|
|
.head-box-btn {
|
|
.head-box-btn {
|
|
|
color: var(--el-color-primary);
|
|
color: var(--el-color-primary);
|
|
|
- font-size: 13px;
|
|
|
|
|
|
|
+ font-size: 12px;
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
- opacity: 0.8;
|
|
|
|
|
&:hover {
|
|
&:hover {
|
|
|
- opacity: 1;
|
|
|
|
|
|
|
+ text-decoration: underline;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
.content-box {
|
|
.content-box {
|
|
|
- height: 300px;
|
|
|
|
|
- overflow: auto;
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
|
|
+ max-height: 400px;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ padding: 8px 0;
|
|
|
.content-box-item {
|
|
.content-box-item {
|
|
|
- padding-top: 12px;
|
|
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- &:last-of-type {
|
|
|
|
|
- padding-bottom: 12px;
|
|
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 12px 16px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ transition: background-color 0.2s;
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ background-color: var(--el-fill-color-light);
|
|
|
}
|
|
}
|
|
|
- .content-box-msg {
|
|
|
|
|
- color: var(--el-text-color-secondary);
|
|
|
|
|
- margin-top: 5px;
|
|
|
|
|
- margin-bottom: 5px;
|
|
|
|
|
|
|
+ .item-icon {
|
|
|
|
|
+ width: 36px;
|
|
|
|
|
+ height: 36px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ margin-right: 12px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ .svg-icon {
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- .content-box-time {
|
|
|
|
|
- color: var(--el-text-color-secondary);
|
|
|
|
|
|
|
+ .item-content {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ .item-title {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
|
|
+ margin-bottom: 4px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .item-time {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- .item-conten {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
|
|
+ .unread-dot {
|
|
|
|
|
+ width: 8px;
|
|
|
|
|
+ height: 8px;
|
|
|
|
|
+ background-color: var(--el-color-danger);
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ margin-left: 8px;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
.foot-box {
|
|
.foot-box {
|
|
|
- height: 35px;
|
|
|
|
|
|
|
+ height: 40px;
|
|
|
color: var(--el-color-primary);
|
|
color: var(--el-color-primary);
|
|
|
font-size: 13px;
|
|
font-size: 13px;
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
- opacity: 0.8;
|
|
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
border-top: 1px solid var(--el-border-color-lighter);
|
|
border-top: 1px solid var(--el-border-color-lighter);
|
|
|
&:hover {
|
|
&:hover {
|
|
|
- opacity: 1;
|
|
|
|
|
|
|
+ background-color: var(--el-fill-color-light);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- :deep(.el-empty__description p) {
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.text-ellipsis {
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.dialog-content-wrapper {
|
|
|
|
|
+ .dialog-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ .dialog-time {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .dialog-main-title {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .dialog-desc-block {
|
|
|
|
|
+ background-color: #f8f8f8;
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .dialog-action-link {
|
|
|
|
|
+ text-align: right;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|