|
@@ -0,0 +1,384 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="message-container">
|
|
|
|
|
+ <div class="message-header">
|
|
|
|
|
+ <div class="title">消息通知</div>
|
|
|
|
|
+ <el-button @click="handleReadAll">全部已读</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 顶部页签 -->
|
|
|
|
|
+ <el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
|
|
|
|
+ <el-tab-pane label="全部消息" name="all"></el-tab-pane>
|
|
|
|
|
+ <el-tab-pane label="未读消息" name="unread"></el-tab-pane>
|
|
|
|
|
+ <el-tab-pane label="拒单/取消" name="reject"></el-tab-pane>
|
|
|
|
|
+ <el-tab-pane label="异常上报" name="anomaly"></el-tab-pane>
|
|
|
|
|
+ </el-tabs>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 消息列表 -->
|
|
|
|
|
+ <div class="message-list" v-loading="loading">
|
|
|
|
|
+ <template v-if="noticeList && noticeList.length > 0">
|
|
|
|
|
+ <div class="message-item" v-for="item in noticeList" :key="item.id"
|
|
|
|
|
+ :class="{ 'is-unread': !(item.readFlag || item.readFlag === '1' || item.readFlag === 1) }"
|
|
|
|
|
+ @click="handleDetail(item)">
|
|
|
|
|
+ <el-badge :is-dot="!(item.readFlag || item.readFlag === '1' || item.readFlag === 1)"
|
|
|
|
|
+ class="icon-badge top-left-dot">
|
|
|
|
|
+ <div class="item-icon" :style="{ backgroundColor: getIconConfig(item).bgColor }">
|
|
|
|
|
+ <svg-icon :icon-class="getIconConfig(item).icon" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-badge>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="item-content">
|
|
|
|
|
+ <div class="item-title">{{ item.title }}</div>
|
|
|
|
|
+ <div class="item-desc">{{ item.content }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="item-time">
|
|
|
|
|
+ {{ parseTime(item.createTime, '{y}-{m}-{d} {h}:{i}') }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <el-empty v-else description="暂无消息数据" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 分页 -->
|
|
|
|
|
+ <div class="pagination-container" v-show="total > 0">
|
|
|
|
|
+ <el-pagination v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize"
|
|
|
|
|
+ layout="total, prev, pager, next, jumper" :total="total" @current-change="getList" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 消息详情对话框 -->
|
|
|
|
|
+ <el-dialog title="消息详情" v-model="detailDialog.open" width="600px" 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>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+// 此代码由AI生成
|
|
|
|
|
+import { ref, reactive, toRefs, getCurrentInstance, ComponentInternalInstance } from 'vue';
|
|
|
|
|
+import { useRouter } from 'vue-router';
|
|
|
|
|
+import { listMyNotice, readNotice, readAllNotice } from '@/api/system/notice';
|
|
|
|
|
+import { NoticeQuery, NoticeVO } from '@/api/system/notice/types';
|
|
|
|
|
+
|
|
|
|
|
+const router = useRouter();
|
|
|
|
|
+// eslint-disable-next-line camelcase
|
|
|
|
|
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 此代码由AI生成
|
|
|
|
|
+ * 通知类型枚举(与后端监听器中的type值保持一致)
|
|
|
|
|
+ * 0 = 派单通知
|
|
|
|
|
+ * 1 = 拒单/取消订单通知
|
|
|
|
|
+ * 2 = 异常上报通知
|
|
|
|
|
+ */
|
|
|
|
|
+const NOTICE_TYPE = {
|
|
|
|
|
+ DISPATCH: 0,
|
|
|
|
|
+ REJECT: 1,
|
|
|
|
|
+ ANOMALY: 2
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const activeTab = ref('all');
|
|
|
|
|
+const noticeList = ref<NoticeVO[]>([]);
|
|
|
|
|
+const loading = ref(true);
|
|
|
|
|
+const total = ref(0);
|
|
|
|
|
+
|
|
|
|
|
+const detailDialog = reactive({
|
|
|
|
|
+ open: false,
|
|
|
|
|
+ title: '',
|
|
|
|
|
+ content: '',
|
|
|
|
|
+ createTime: '',
|
|
|
|
|
+ tagLabel: '',
|
|
|
|
|
+ tagType: '' as any,
|
|
|
|
|
+ type: undefined as number | string | undefined,
|
|
|
|
|
+ businessId: undefined as number | string | undefined
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const data = reactive({
|
|
|
|
|
+ queryParams: {
|
|
|
|
|
+ pageNum: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ title: undefined,
|
|
|
|
|
+ type: undefined,
|
|
|
|
|
+ readFlag: undefined
|
|
|
|
|
+ } as NoticeQuery
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const { queryParams } = toRefs(data);
|
|
|
|
|
+
|
|
|
|
|
+/** 此代码由AI生成 - 根据type枚举获取图标和颜色配置 */
|
|
|
|
|
+const getIconConfig = (item: any) => {
|
|
|
|
|
+ const type = item.type;
|
|
|
|
|
+ if (type === NOTICE_TYPE.ANOMALY) return { icon: 'bug', bgColor: '#f56c6c' };
|
|
|
|
|
+ if (type === NOTICE_TYPE.REJECT) return { icon: 'message-center', bgColor: '#f56c6c' };
|
|
|
|
|
+ if (type === NOTICE_TYPE.DISPATCH) return { icon: 'guide', bgColor: '#409eff' };
|
|
|
|
|
+ return { icon: 'message-center', bgColor: '#909399' };
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/** 查询我的消息列表 */
|
|
|
|
|
+function getList() {
|
|
|
|
|
+ loading.value = true;
|
|
|
|
|
+ listMyNotice(queryParams.value).then((response: any) => {
|
|
|
|
|
+ noticeList.value = response.rows;
|
|
|
|
|
+ total.value = response.total;
|
|
|
|
|
+ loading.value = false;
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 此代码由AI生成 - 切换页签(使用type枚举过滤) */
|
|
|
|
|
+function handleTabClick(tab: any) {
|
|
|
|
|
+ queryParams.value.pageNum = 1;
|
|
|
|
|
+ queryParams.value.type = undefined;
|
|
|
|
|
+ queryParams.value.title = undefined;
|
|
|
|
|
+ queryParams.value.readFlag = undefined;
|
|
|
|
|
+
|
|
|
|
|
+ const paneName = tab.paneName as string;
|
|
|
|
|
+ if (paneName === 'unread') {
|
|
|
|
|
+ queryParams.value.readFlag = false as any;
|
|
|
|
|
+ } else if (paneName === 'reject') {
|
|
|
|
|
+ queryParams.value.type = NOTICE_TYPE.REJECT as any;
|
|
|
|
|
+ } else if (paneName === 'anomaly') {
|
|
|
|
|
+ queryParams.value.type = NOTICE_TYPE.ANOMALY as any;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ getList();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 此代码由AI生成 - 消息详情点击与设为已读(使用type枚举判断) */
|
|
|
|
|
+function handleDetail(item: NoticeVO) {
|
|
|
|
|
+ detailDialog.title = item.title || '';
|
|
|
|
|
+ detailDialog.content = item.content || '';
|
|
|
|
|
+ detailDialog.createTime = proxy?.parseTime(item.createTime, '{y}-{m}-{d} {h}:{i}') || '';
|
|
|
|
|
+ detailDialog.type = item.type;
|
|
|
|
|
+ detailDialog.businessId = item.businessId;
|
|
|
|
|
+
|
|
|
|
|
+ const type = item.type;
|
|
|
|
|
+ if (type === NOTICE_TYPE.ANOMALY) {
|
|
|
|
|
+ detailDialog.tagLabel = '异常上报';
|
|
|
|
|
+ detailDialog.tagType = 'danger';
|
|
|
|
|
+ } else if (type === NOTICE_TYPE.REJECT) {
|
|
|
|
|
+ detailDialog.tagLabel = '取消订单';
|
|
|
|
|
+ detailDialog.tagType = 'danger';
|
|
|
|
|
+ } else if (type === NOTICE_TYPE.DISPATCH) {
|
|
|
|
|
+ detailDialog.tagLabel = '新订单';
|
|
|
|
|
+ detailDialog.tagType = 'primary';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ detailDialog.tagLabel = '系统消息';
|
|
|
|
|
+ detailDialog.tagType = 'info';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ detailDialog.open = true;
|
|
|
|
|
+
|
|
|
|
|
+ // 如果状态未读,发请求
|
|
|
|
|
+ if (!(item.readFlag || item.readFlag === '1' || item.readFlag === 1)) {
|
|
|
|
|
+ readNotice(item.id as number).then(() => {
|
|
|
|
|
+ item.readFlag = true as any; // 本地变更为已读
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 此代码由AI生成 - 前往处理(使用type枚举判断跳转路由) */
|
|
|
|
|
+function handleProcess() {
|
|
|
|
|
+ detailDialog.open = false;
|
|
|
|
|
+ const type = detailDialog.type;
|
|
|
|
|
+ if (type === NOTICE_TYPE.ANOMALY) {
|
|
|
|
|
+ router.push({ path: '/fulfiller/anamaly', query: { id: detailDialog.businessId } });
|
|
|
|
|
+ } else if (type === NOTICE_TYPE.REJECT || type === NOTICE_TYPE.DISPATCH) {
|
|
|
|
|
+ router.push({ path: '/order/orderList', query: { id: detailDialog.businessId } });
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 全部标为已读操作 */
|
|
|
|
|
+function handleReadAll() {
|
|
|
|
|
+ proxy?.$modal.confirm('是否确认将全部未读消息标记为已读?').then(function () {
|
|
|
|
|
+ return readAllNotice();
|
|
|
|
|
+ }).then(() => {
|
|
|
|
|
+ proxy?.$modal.msgSuccess('全部已读操作成功');
|
|
|
|
|
+ getList();
|
|
|
|
|
+ }).catch(() => { });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+getList();
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+.message-container {
|
|
|
|
|
+ padding: 24px;
|
|
|
|
|
+ background-color: #fff;
|
|
|
|
|
+ margin: 20px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
|
|
|
+
|
|
|
|
|
+ .message-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 24px;
|
|
|
|
|
+
|
|
|
|
|
+ .title {
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 隐藏页签下方的灰线,按照参考图页签非常简洁 */
|
|
|
|
|
+ :deep(.el-tabs__nav-wrap::after) {
|
|
|
|
|
+ background-color: #f0f0f0;
|
|
|
|
|
+ height: 1px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .message-list {
|
|
|
|
|
+ min-height: 400px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .message-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ padding: 20px 24px;
|
|
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ background-color: #fff;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ transition: background-color 0.3s ease;
|
|
|
|
|
+
|
|
|
|
|
+ &.is-unread {
|
|
|
|
|
+ // 图中未读消息带有浅绿偏黄的背景色
|
|
|
|
|
+ background-color: #f3faee;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ background-color: #f5f7fa;
|
|
|
|
|
+
|
|
|
|
|
+ &.is-unread {
|
|
|
|
|
+ background-color: #ecf3e5; // 微调悬停效果
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 取消最后一条的边框
|
|
|
|
|
+ &:last-child {
|
|
|
|
|
+ border-bottom: none;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .icon-badge.top-left-dot {
|
|
|
|
|
+ margin-right: 20px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-badge__content.is-fixed.is-dot) {
|
|
|
|
|
+ right: auto;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ transform: translateY(-30%) translateX(-30%);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .item-icon {
|
|
|
|
|
+ width: 48px;
|
|
|
|
|
+ height: 48px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+
|
|
|
|
|
+ .svg-icon {
|
|
|
|
|
+ font-size: 24px;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .item-content {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+
|
|
|
|
|
+ .item-title {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ line-height: 1.4;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .item-desc {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+ // 单行截断展示如果太长可以放开注释
|
|
|
|
|
+ // white-space: nowrap;
|
|
|
|
|
+ // overflow: hidden;
|
|
|
|
|
+ // text-overflow: ellipsis;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .item-time {
|
|
|
|
|
+ width: 150px;
|
|
|
|
|
+ text-align: right;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-family: monospace;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .pagination-container {
|
|
|
|
|
+ margin-top: 24px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 消息详情对话框内部样式 */
|
|
|
|
|
+.dialog-content-wrapper {
|
|
|
|
|
+ padding: 0 10px;
|
|
|
|
|
+
|
|
|
|
|
+ .dialog-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+
|
|
|
|
|
+ .dialog-time {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .dialog-main-title {
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .dialog-desc-block {
|
|
|
|
|
+ background-color: #f8f8f8;
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .dialog-action-link {
|
|
|
|
|
+ text-align: right;
|
|
|
|
|
+
|
|
|
|
|
+ .el-link {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|