Browse Source

Merge branch 'master' of http://39.106.8.246:3003/web/wanda-adm

haojianlong 4 years ago
parent
commit
adf8a6b044

+ 1 - 0
.env.development

@@ -1,2 +1,3 @@
 NODE_ENV = "development"
 VUE_APP_BASE_API = 'https://vue-typescript-admin-mock-server.armour.now.sh/mock-api/v1/'
+VUE_APP_MQTT_SERVICE = 'ws://adm.sagacloud.cn/stomp/'

+ 1 - 0
.env.production

@@ -1,2 +1,3 @@
 NODE_ENV = "production"
 VUE_APP_BASE_API = 'https://vue-typescript-admin-mock-server.armour.now.sh/mock-api/v1/'
+VUE_APP_MQTT_SERVICE = 'ws://adm.sagacloud.cn/stomp/'

+ 1 - 0
.env.staging

@@ -1,4 +1,5 @@
 NODE_ENV = "production"
 # Base api
 VUE_APP_BASE_API = '/stage-api'
+VUE_APP_MQTT_SERVICE = 'ws://adm.sagacloud.cn/stomp/'
 

+ 1 - 1
public/index.html

@@ -5,7 +5,7 @@
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width,initial-scale=1.0">
   <link rel="icon" href="<%= BASE_URL %>favicon.ico">
-  <link rel="stylesheet" href="//at.alicdn.com/t/font_2327510_n21nvmc2c1p.css" />
+  <link rel="stylesheet" href="//at.alicdn.com/t/font_2327510_l3fgpus693i.css" />
   <title><%= htmlWebpackPlugin.options.title %></title>
 </head>
 <body>

+ 51 - 0
src/api/messagecenter.ts

@@ -0,0 +1,51 @@
+/**
+ * 消息中心接口文档
+ */
+import httputils from "@/api/httputils";
+const baseUrl = "/message-center";
+
+/**
+ * title: 统计数量
+ * info: 查询总数量不传参数,查询 已读Read = true,未读 Read = false
+ */
+export function messgeCount(postParams: any): Promise<any> {
+    return httputils.postJson(`${baseUrl}/message/count`, postParams);
+}
+
+/**
+ * title: 创建消息
+ * info: 消息类型:提示(Info)、警告(Warning)、错误(Error)、成功(Success)
+ */
+export function messgeCreate(postParams: any): Promise<any> {
+    return httputils.postJson(`${baseUrl}/message/create`, postParams);
+}
+
+/**
+ * title: 删除消息
+ */
+export function messgeDelete(postParams: any): Promise<any> {
+    return httputils.postJson(`${baseUrl}/message/delete`, postParams);
+}
+
+/**
+ * title: 查询消息列表
+ */
+export function messgeQuery(postParams: any): Promise<any> {
+    return httputils.postJson(`${baseUrl}/message/page-query`, postParams);
+}
+
+/**
+ * title: 更新消息状态
+ * info: 是否读过:未读(f)、已读(t)
+ */
+export function messgeUpdate(postParams: any): Promise<any> {
+    return httputils.postJson(`${baseUrl}/message/update`, postParams);
+}
+
+/**
+ * title: 根据接收人将消息置为已读
+ * info: 已读Read = true,*UserId
+ */
+export function messgeUpdateState(postParams: any): Promise<any> {
+    return httputils.postJson(`${baseUrl}/message/update-state`, postParams);
+}

+ 0 - 3
src/components/GlobalUploader/index.vue

@@ -1,8 +1,5 @@
 <!--
  * @Author: zhangyu <taohuzy@163.com>
- * @Date: 2019-11-28 10:05:28
- * @Info: 
- * @LastEditTime : 2020-01-03 15:31:04
  -->
 <template>
     <div id="global-uploader">

+ 272 - 0
src/layout/components/Navbar/MessageSever/MsgAllDetails.vue

@@ -0,0 +1,272 @@
+<template>
+    <div class="box">
+        <div class="condition">
+            <div class="main" :style="tableData && tableData.length ? 'height: calc(100% - 50px);' : 'height: calc(100% - 46px);'">
+                <el-table
+                    ref="multipleTable"
+                    :data="tableData"
+                    v-loading="loading"
+                    stripe
+                    height="100%"
+                    :header-cell-style="headerStyle"
+                    @expand-change="changeMessageState"
+                >
+                    <el-table-column type="selection" width="55"></el-table-column>
+                    <el-table-column width="25">
+                        <template slot-scope="scope">
+                            <el-badge class="item" type="warning" is-dot v-if="!scope.row.read"></el-badge>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="title" label="标题内容" min-width="300" show-overflow-tooltip></el-table-column>
+                    <el-table-column label="项目">
+                        <template slot-scope="scope">
+                            {{ scope.row.project[0].localName }}
+                        </template>
+                    </el-table-column>
+                    <el-table-column label="模块" width="200">
+                        <template slot-scope="scope">
+                            {{ moduleMap[scope.row.module] ? moduleMap[scope.row.module] : scope.row.module }}
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="createTime" label="消息时间"> </el-table-column>
+                    <el-table-column label="消息类型">
+                        <template slot-scope="scope">
+                            <div>
+                                <i
+                                    :class="iconClassMap[scope.row.type] ? iconClassMap[scope.row.type] : 'msg-icon el-icon-warning warning-color'"
+                                    style="font-size: 14px"
+                                ></i>
+                                <span>{{ typeMap[scope.row.type] ? typeMap[scope.row.type] : scope.row.type }}</span>
+                            </div>
+                        </template>
+                    </el-table-column>
+                    <el-table-column type="expand">
+                        <template slot-scope="scope">
+                            <p>{{ scope.row.content.message }}</p>
+                            <el-link
+                                v-for="(btn, index) in scope.row.content.buttonList"
+                                style="float: left; font-size: 12px; color: white"
+                                :key="index"
+                                :href="`/image-service/common/file_get?systemId=revit&key=${btn.url}`"
+                                :download="btn.fileName ? btn.fileName : ''"
+                            >
+                                {{ btn.name }}
+                            </el-link>
+                        </template>
+                    </el-table-column>
+                    <template slot="empty">
+                        <div style="height: 60%; transform: translateY(50%)">
+                            <i class="icon-wushuju iconfont"></i>
+                            暂无数据
+                        </div>
+                    </template>
+                </el-table>
+            </div>
+            <el-pagination
+                class="right"
+                v-show="tableData && tableData.length"
+                @size-change="handleSizeChange"
+                @current-change="handleCurrentChange"
+                :current-page="page.pageNumber"
+                :page-sizes="page.pageSizes"
+                :page-size="page.pageSize"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="page.total"
+            >
+            </el-pagination>
+        </div>
+    </div>
+</template>
+
+<script lang="ts">
+import { messgeQuery, messgeUpdate } from "@/api/messagecenter";
+import { Component, Vue } from "vue-property-decorator";
+import Bus from "@/utils/bus.ts";
+import { UserModule } from "@/store/modules/user";
+
+@Component({
+    name: "MsgAllDetails",
+    components: {},
+})
+export default class extends Vue {
+    // 消息数据
+    private tableData: any[] = [];
+    // loading标识
+    private loading = false;
+    // 分页配置
+    private page = {
+        pageSize: 50,
+        pageSizes: [10, 20, 50, 100],
+        pageNumber: 1,
+        total: 0,
+    };
+    // 列表样式
+    private headerStyle = {
+        backgroundColor: "#e1e4e5",
+        color: "#2b2b2b",
+        lineHeight: "30px",
+    };
+    // 消息模块映射
+    private moduleMap = {
+        Model: "模型文件管理",
+    };
+    // 消息类型图标映射
+    private iconClassMap = {
+        Success: "msg-icon el-icon-success success-color",
+        Error: "msg-icon el-icon-error error-color",
+        Warning: "msg-icon el-icon-warning warning-color",
+        Info: "msg-icon el-icon-info info-color",
+    };
+    // 消息类型文字映射
+    private typeMap = {
+        Success: "成功",
+        Error: "错误",
+        Warning: "警告",
+        Info: "提醒",
+    };
+
+    get userId(): string {
+        return UserModule.userId;
+    }
+
+    mounted() {
+        this.getTableData();
+    }
+
+    /**
+     * 获取列表数据
+     */
+    private async getTableData() {
+        let params = {
+            cascade: [
+                {
+                    name: "project",
+                },
+            ],
+            filters: `userId='${this.userId}';type!='Refresh'`,
+            orders: "createTime desc, id asc",
+            pageNumber: this.page.pageNumber,
+            pageSize: this.page.pageSize,
+        };
+        this.loading = true;
+        const res = await messgeQuery(params);
+        if (res.result === "success") {
+            this.tableData = res.content;
+            this.page.total = res.total;
+            this.loading = false;
+        }
+    }
+
+    /**
+     * 切换每页显示数量
+     */
+    private handleSizeChange(val: number) {
+        this.page.pageSize = val;
+        this.getTableData();
+    }
+
+    /**
+     * 切换页码
+     */
+    private handleCurrentChange(val: number) {
+        this.page.pageNumber = val;
+        this.getTableData();
+    }
+
+    /**
+     * 更新消息状态:已读、未读
+     */
+    private async changeMessageState(val: any) {
+        if (!val.Read) {
+            val.Read = true;
+            const params = {
+                content: [
+                    {
+                        id: val.id,
+                        read: true,
+                    },
+                ],
+            };
+            const res = await messgeUpdate(params);
+            // 展开更新为已读状态,未读数量同步变更
+            if (res.result === "success") Bus.$emit("getUnreadCountUpdate");
+        }
+    }
+}
+</script>
+
+<style scoped lang='scss'>
+.box {
+    height: 100%;
+    .condition {
+        padding: 10px;
+        display: flex;
+        height: 100%;
+        flex-direction: column;
+        border: 1px solid #dfe6ec;
+        background: #fff;
+
+        .main {
+            margin-top: 10px;
+
+            a {
+                text-decoration: none;
+            }
+        }
+
+        .right {
+            text-align: right;
+            margin-top: 10px;
+        }
+    }
+}
+
+.el-badge.item {
+    height: 10px;
+}
+
+.el-table__expanded-cell {
+    p {
+        margin-left: 40px;
+    }
+}
+
+.el-link {
+    margin: 20px 0 0 40px;
+    background: #409eff;
+    border-radius: 3px;
+    padding: 3px 15px;
+}
+
+.demo-table-expand {
+    font-size: 0;
+}
+
+.demo-table-expand label {
+    width: 90px;
+    color: #99a9bf;
+}
+
+.demo-table-expand .el-form-item {
+    margin-right: 0;
+    margin-bottom: 0;
+    margin-left: 120px;
+    width: 100%;
+}
+
+.success-color {
+    color: #67c23a;
+}
+
+.error-color {
+    color: #f56c6c;
+}
+
+.warning-color {
+    color: #e6a23c;
+}
+
+.info-color {
+    color: #909399;
+}
+</style>

+ 521 - 0
src/layout/components/Navbar/MessageSever/index.vue

@@ -0,0 +1,521 @@
+<!--
+ * @Author: zhangyu <taohuzy@163.com>
+ -->
+<template>
+    <div class="notification-box" v-clickOutside="handleClose" @click="handleClickRead">
+        <el-badge :value="unreadNum" :hidden="!unreadNum" :max="99">
+            <i class="el-icon-message-solid"></i>
+        </el-badge>
+        <transition name="el-fade-in-linear">
+            <div v-show="noticeListShow" @click.stop="" class="noticeBox">
+                <ul class="noticeTab">
+                    <li class="noticeTab_item">
+                        消息通知
+                        <!-- <span class="unread_num">1</span> -->
+                    </li>
+                </ul>
+                <div class="noticeList_scroll">
+                    <el-scrollbar style="height: 100%">
+                        <ul class="noticeList">
+                            <li
+                                class="noticeList_item"
+                                v-for="item in messageList"
+                                :key="item.id"
+                                :title="item.content.message ? item.content.message : ''"
+                            >
+                                <div class="noticeItem_box">
+                                    <div class="noticeItem_text">
+                                        <i :class="iconClassMap[item.type] ? iconClassMap[item.type] : 'msg-icon el-icon-warning warning-color'"></i>
+                                        <p>
+                                            <span>{{ `【${moduleMap[item.module] ? moduleMap[item.module] : item.module}】 ` }}</span
+                                            >{{ item.title ? item.title : "" }}
+                                        </p>
+                                    </div>
+                                    <div class="noticeItem_time">
+                                        <span class="proname">{{ item.project[0].localName || "" }}</span>
+                                        {{ item.createTime }}
+                                        <template v-if="item.content.buttonList.length">
+                                            <el-link
+                                                v-for="(btn, index) in item.content.buttonList"
+                                                style="float: right; font-size: 12px; margin-left: 5px"
+                                                type="primary"
+                                                :key="index"
+                                                :href="`/image-service/common/file_get?systemId=revit&key=${btn.url}`"
+                                                :download="btn.fileName ? btn.fileName : ''"
+                                            >
+                                                {{ btn.name ? btn.name : "" }}
+                                            </el-link>
+                                        </template>
+                                    </div>
+                                </div>
+                            </li>
+                        </ul>
+                    </el-scrollbar>
+                </div>
+                <div class="notice_operate">
+                    <!-- <div class="readAll">全部标为已读</div> -->
+                    <div class="seeAll" @click="allDetails">查看全部<i class="iconfont icon-right"></i></div>
+                </div>
+            </div>
+        </transition>
+        <!-- <el-button type="primary" @click="handleClickConnectMQ">连接MQ</el-button>
+    <el-button type="primary" @click="handleClickDisconnectMQ">断开MQ</el-button>
+    <el-button type="primary" @click="handleClickUnsubscribe">停止接收消息</el-button><br><br>
+    <el-input type="textarea" :autosize="{ minRows: 6, maxRows: 6}" v-model="sendMessage" placeholder="请输入要发送的内容"></el-input>
+    <br><br>
+    <el-button type="primary" @click="handleClickSendMessage">发送消息</el-button>
+    <el-card style="margin-top:20px;text-align:left;">
+      <div slot="header" class="clearfix">
+        <span>消息历史</span>
+      </div>
+      <div v-for="(message, index) in messageList" :key="index" class="text item">
+        {{ message }}
+      </div>
+    </el-card> -->
+    </div>
+</template>
+
+<script lang="ts">
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+import Stomp from "stompjs";
+import { Component, Vue } from "vue-property-decorator";
+import { UserModule } from "@/store/modules/user";
+import Msmq from "./msmq";
+import Bus from "@/utils/bus";
+import { MQTT_SERVICE, MQTT_USERNAME, MQTT_PASSWORD } from "./mqSetting";
+import { messgeCount, messgeQuery, messgeUpdateState } from "@/api/messagecenter";
+
+@Component({
+    name: "MessageSever",
+    components: {},
+})
+export default class extends Vue {
+    // STOMP客户端对象
+    private client = Stomp.client(MQTT_SERVICE);
+    // 未读消息数量
+    private unreadNum = 0;
+    // 消息列表是否显示标志
+    private noticeListShow = false;
+    // 发送的消息内容
+    private sendMessage = "";
+    // 历史消息
+    private messageList = [];
+    // 订阅的消息实例
+    private subList: any[] = [];
+    // 订阅的消息名称
+    private topics: string[] = ["/topic/message.manage"];
+    // 消息模块映射
+    private moduleMap = {
+        Model: "模型文件管理",
+    };
+    // 消息类型图标映射
+    private iconClassMap = {
+        Success: "msg-icon el-icon-success success-color",
+        Error: "msg-icon el-icon-error error-color",
+        Warning: "msg-icon el-icon-warning warning-color",
+        Info: "msg-icon el-icon-info info-color",
+    };
+
+    get userId(): string {
+        return UserModule.userId;
+    }
+
+    get username(): string {
+        return UserModule.username;
+    }
+
+    get projectId(): string {
+        return UserModule.projectId;
+    }
+
+    get projects(): any[] {
+        return UserModule.projects;
+    }
+
+    created() {
+        this.connect();
+    }
+    mounted() {
+        this.getUnreadCount();
+        this.getMessageList(); //获取消息列表
+        Bus.$on("messageListUpdate", () => {
+            this.getMessageList();
+        });
+        Bus.$on("getUnreadCountUpdate", () => {
+            this.getUnreadCount();
+        });
+    }
+
+    /**
+     * 获取消息列表(最新10条)
+     */
+    private async getMessageList() {
+        const params = {
+            cascade: [
+                {
+                    name: "project",
+                },
+            ],
+            filters: `userId='${this.userId}';type!='refresh'`,
+            orders: "createTime desc, id asc",
+            pageNumber: 1,
+            pageSize: 10,
+        };
+        const res = await messgeQuery(params);
+        this.messageList = res.content || [];
+    }
+
+    /**
+     * 获取未读消息的数量
+     */
+    private async getUnreadCount() {
+        let params = {
+            filters: `read=false;userId='${this.userId}';type!='refresh'`,
+        };
+        const res = await messgeCount(params);
+        this.unreadNum = res.count || 0;
+    }
+
+    /**
+     * 将当前角色消息全部置为已读
+     */
+    private async setAllRead() {
+        if (this.userId) {
+            const res = await messgeUpdateState({ read: true, userId: this.userId });
+            if (res.result === "success") this.unreadNum = 0;
+        }
+    }
+
+    /**
+     * 关闭消息列表
+     */
+    handleClose(e: Event) {
+        this.noticeListShow = false;
+        return e;
+    }
+
+    /**
+     * 点击消息铃铛
+     */
+    private handleClickRead() {
+        this.noticeListShow ? (this.noticeListShow = false) : (this.noticeListShow = true);
+        if (this.noticeListShow) {
+            this.setAllRead(); //将当前角色消息全部置为已读
+        }
+    }
+
+    /**
+     * 连接STOMP服务端
+     */
+    private connect() {
+        this.client = Stomp.client(MQTT_SERVICE);
+        this.client.reconnect_delay = 5000;
+        var clientid = `sagaMQ-${this.username}-${new Date().getTime()}`;
+        var headers = {
+            login: MQTT_USERNAME,
+            passcode: MQTT_PASSWORD,
+            "client-id": clientid,
+        };
+        this.client.connect(headers, this.onConnected, this.onFailed);
+    }
+
+    private onConnected(frame: any) {
+        console.log("Connected: " + frame);
+        //订阅多个消息
+        this.topics.forEach((item: string) => {
+            const sub: any = this.client.subscribe(item, this.onmessage, this.onFailed);
+            this.subList.push(sub);
+        });
+        // this.client.subscribe(topic, this.onmessage, this.onFailed)
+    }
+
+    /**
+     * 接收到消息的回调
+     */
+    private onmessage(message: string) {
+        this.unreadNum = Msmq.handleMsg(message, this.projects, this.userId, this.unreadNum);
+    }
+
+    /**
+     * 接收消息失败回调
+     */
+    private onFailed(frame: any) {
+        console.log("Failed: " + frame);
+    }
+
+    /**
+     * 停止接收消息
+     */
+    private unsubscribe() {
+        this.subList.forEach((item: any) => {
+            item.unsubscribe();
+        });
+    }
+
+    /**
+     * 断开连接
+     */
+    private disconnect() {
+        this.client.disconnect(function () {
+            console.log("连接已断开!");
+        });
+    }
+
+    /**
+     * 发送消息
+     */
+    private send(destination: string, message: any, headers = {}) {
+        this.client.send(destination, headers, JSON.stringify(message));
+    }
+
+    /**
+     * 连接MQ
+     */
+    private handleClickConnectMQ() {
+        this.connect();
+    }
+
+    /**
+     * 断开MQ
+     */
+    private handleClickDisconnectMQ() {
+        this.disconnect();
+    }
+
+    /**
+     * 停止接收消息
+     */
+    private handleClickUnsubscribe() {
+        this.unsubscribe();
+    }
+
+    /**
+     * 发送消息
+     */
+    private handleClickSendMessage() {
+        this.send("/topic/datacenter.broadcast", this.sendMessage);
+    }
+
+    /**
+     * 查看完整消息列表(跳转消息列表页面)
+     */
+    private allDetails() {
+        this.noticeListShow = false;
+        this.$router.push({ path: "/allDetails/index" });
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.notification-box {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    line-height: 1;
+    padding: 16px 10px 12px;
+    box-sizing: border-box;
+    color: #79869a;
+    cursor: pointer;
+    .noticeBox {
+        position: absolute;
+        top: 50px;
+        left: 50%;
+        margin-left: -220px;
+        cursor: auto;
+        z-index: 1000;
+        text-align: left;
+        width: 272px;
+        height: 362px;
+        box-shadow: 0 1px 6px 0 #ccc;
+        color: #333;
+        background: #fff;
+        overflow: visible !important;
+    }
+    .noticeBox::before {
+        content: "";
+        border: solid 7px transparent;
+        border-bottom-color: #fff;
+        display: block;
+        position: absolute;
+        width: 2px;
+        top: -14px;
+        left: 50%;
+        margin-left: 77px;
+        z-index: 0;
+    }
+    .noticeTab {
+        padding: 8px 16px 0;
+        z-index: 10;
+        height: 40px;
+        zoom: 1;
+        box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.1) inset;
+        .noticeTab_item {
+            position: relative;
+            width: 240px;
+            height: 32px;
+            line-height: 32px;
+            padding: 0;
+            margin-right: 0;
+            font-weight: 700;
+            text-align: left;
+            cursor: pointer;
+            -webkit-transition: all 0.2s ease-in-out;
+            -o-transition: all 0.2s ease-in-out;
+            transition: all 0.2s ease-in-out;
+            .unread_num {
+                display: inline-block;
+                color: #fff;
+                background-color: #5182e4;
+                height: 14px;
+                padding: 0 2px;
+                line-height: 14px;
+                text-align: center;
+                border-radius: 2px;
+                font-size: 12px;
+                padding: 0 3px;
+                font-family: Arial;
+                -webkit-transform: scale(0.85);
+                -ms-transform: scale(0.85);
+                -o-transform: scale(0.85);
+                transform: scale(0.85);
+            }
+        }
+        .active {
+            box-shadow: 0 -2px 0 0 #5182e4 inset;
+        }
+    }
+    .noticeList_scroll {
+        height: 282px;
+        ::v-deep .el-scrollbar__wrap {
+            overflow-x: hidden;
+        }
+        .noticeList_item {
+            width: 100%;
+            height: auto;
+            position: relative;
+            cursor: pointer;
+            box-sizing: border-box;
+            padding: 12px 16px;
+            -webkit-transition: all 0.2s ease-in-out;
+            -o-transition: all 0.2s ease-in-out;
+            transition: all 0.2s ease-in-out;
+            zoom: 1;
+            .noticeItem_box {
+                float: left;
+                position: relative;
+                width: 100%;
+                .noticeItem_text {
+                    color: rgba(10, 18, 32, 0.87);
+                    font-weight: 700;
+                    padding-left: 18px;
+                    text-indent: -6px;
+                    position: relative;
+                    font-size: 12px;
+                    line-height: 17px;
+                    -webkit-transition: all 0.2s ease-in-out;
+                    -o-transition: all 0.2s ease-in-out;
+                    transition: all 0.2s ease-in-out;
+                    .msg-icon {
+                        position: absolute;
+                        top: 2.5px;
+                        left: 6px;
+                    }
+                    p {
+                        word-break: break-all;
+                    }
+                }
+                .noticeItem_time {
+                    font-size: 12px;
+                    height: 19px;
+                    line-height: 19px;
+                    color: rgba(10, 18, 32, 0.46);
+                    margin-top: 4px;
+                    padding-left: 18px;
+                    .proname {
+                        float: left;
+                        display: block;
+                        width: 80px;
+                        overflow: hidden;
+                        white-space: nowrap;
+                        text-overflow: ellipsis;
+                    }
+                }
+            }
+        }
+        .noticeList_item:hover {
+            background-color: #f5f7f7;
+        }
+        .noticeList_item:after {
+            content: "";
+            display: block;
+            height: 0;
+            clear: both;
+            visibility: hidden;
+        }
+        .noticeList_item:before {
+            position: absolute;
+            right: 0;
+            bottom: 0;
+            left: 0;
+            height: 1px;
+            padding: 0 16px;
+            background-color: #ebebeb;
+            content: "";
+            background-clip: content-box;
+        }
+    }
+    .notice_operate {
+        height: 40px;
+        line-height: 20px;
+        padding: 10px 16px;
+        box-sizing: border-box;
+        background-color: rgba(242, 243, 245, 0.48);
+        box-shadow: inset 0 1px 0 0 rgba(10, 18, 32, 0.06);
+        zoom: 1;
+        .readAll {
+            float: left;
+            cursor: pointer;
+            -webkit-transition: all 0.2s ease-in-out;
+            -o-transition: all 0.2s ease-in-out;
+            transition: all 0.2s ease-in-out;
+        }
+        .readAll:hover {
+            text-decoration: underline;
+        }
+        .seeAll {
+            float: right;
+            color: #5182e4;
+            cursor: pointer;
+            font-weight: 700;
+        }
+        .seeAll:hover {
+            text-decoration: underline;
+        }
+    }
+    .el-icon-message-solid {
+        font-size: 22px;
+    }
+    ::v-deep .el-badge__content {
+        height: 16px;
+        line-height: 16px;
+        border: 1px solid transparent;
+    }
+}
+.notification-box:hover {
+    color: #d3d8e2;
+    background-color: #3f4f62;
+}
+.success-color {
+    color: #67c23a;
+}
+.error-color {
+    color: #f56c6c;
+}
+.warning-color {
+    color: #e6a23c;
+}
+.info-color {
+    color: #909399;
+}
+</style>

+ 8 - 0
src/layout/components/Navbar/MessageSever/mqSetting.ts

@@ -0,0 +1,8 @@
+/**
+ * @Author zhangyu <taohuzy@163.com>
+ * @info MQ链接配置文件
+ * 
+ */
+export const MQTT_SERVICE = process.env.VUE_APP_MQTT_SERVICE // mq服务地址
+export const MQTT_USERNAME = 'admin' // mq连接用户名
+export const MQTT_PASSWORD = 'admin' // mq连接密码

+ 46 - 0
src/layout/components/Navbar/MessageSever/msmq.ts

@@ -0,0 +1,46 @@
+import { Notification } from 'element-ui';
+import Bus from '@/utils/bus'
+
+class MSMQ {
+    static msgTypeMap: any =  {
+    "Success": "success",
+    "Error": "error",
+    "Warning": "warning",
+    "Info": "info",
+  }
+
+  static moduleMap: any =  {//消息模块映射
+    "Model": "模型文件管理"
+  }
+  
+  static handleMsg (message: any, projects: any[], userId: string, unreadNum: number) {
+    let data: any
+    try {
+      data = JSON.parse(message.body)
+    } catch (err) {
+      return 0;
+    }
+    if(data.ProjectId && projects.some((item) => {return item.id == data.ProjectId})) { //判断消息是否是当前用户拥有的项目
+      if(!data.UserList || (userId && data.UserList && data.UserList.includes(userId))){//判断消息接收人是否是当前用户
+        if(data.Module && data.Module == 'Model'){ //模型文件的消息触发模型文件列表刷新事件
+          Bus.$emit('modelStatusChange', data)
+        }
+        if(data.Type != 'Refresh'){//消息列表可见消息(注类型为'Refresh'的消息用来驱动对应服务刷新,消息中心[用户]不可见)
+          Notification({ 
+            title: `${data.Title?data.Title:''}`, 
+            dangerouslyUseHTMLString: true, 
+            message: `【${this.moduleMap[data.Module]?this.moduleMap[data.Module]:data.Module}】${data.Content?data.Content.Message?data.Content.Message:'':''}`,
+            type: this.msgTypeMap[data.Type]?this.msgTypeMap[data.Type]:'info'
+          })
+          // Notification.info({ title: '消息',dangerouslyUseHTMLString: true, message: `项目编号:${data.projectId}<br>消息类型:${data.type}<br>描述信息:${data.description}<br>` })
+          unreadNum++
+          Bus.$emit('messageListUpdate', data)//消息驱动消息列表刷新
+        }
+        return unreadNum
+      }
+    }
+    return 0;
+  }
+}
+
+export default MSMQ

+ 17 - 4
src/layout/components/Navbar/index.vue

@@ -1,12 +1,17 @@
 <template>
     <div class="navbar">
-        <span class="title" v-show="sidebar.opened">万达数字化运维平台</span>
+        <span class="title" v-show="sidebar.opened"><i class="iconfont icon-wandajituan"></i></span>
         <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggle-click="toggleSideBar" />
 
         <div class="right-menu">
+            <ul class="header-nav">
+                <li class="header-nav-notification">
+                    <message-sever></message-sever>
+                </li>
+            </ul>
             <el-dropdown class="avatar-container right-menu-item hover-effect">
                 <div class="avatar-wrapper">
-                    <span> {{username}} </span>
+                    <span> {{ username }} </span>
                     <i class="el-icon-user-solid" />
                 </div>
                 <el-dropdown-menu slot="dropdown">
@@ -25,12 +30,14 @@ import { AppModule } from "@/store/modules/app";
 import { UserModule } from "@/store/modules/user";
 import Breadcrumb from "@/components/Breadcrumb/index.vue";
 import Hamburger from "@/components/Hamburger/index.vue";
+import MessageSever from "./MessageSever/index.vue";
 
 @Component({
     name: "Navbar",
     components: {
         Breadcrumb,
         Hamburger,
+        MessageSever,
     },
 })
 export default class extends Vue {
@@ -64,7 +71,7 @@ export default class extends Vue {
 <style lang="scss" scoped>
 .navbar {
     height: 50px;
-    overflow: hidden;
+    // overflow: hidden;
     position: relative;
     background-color: #2b3643;
     box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
@@ -72,11 +79,16 @@ export default class extends Vue {
 
     .title {
         float: left;
-        padding: 0 20px;
+        padding: 3px 50px 0 20px;
         margin: 0 auto;
         line-height: 46px;
         height: 100%;
         color: #fff;
+        i.icon-wandajituan {
+            color: #0861ab;
+            padding: 8px 0;
+            font-size: 36px;
+        }
     }
 
     .hamburger-container {
@@ -99,6 +111,7 @@ export default class extends Vue {
         float: right;
         height: 100%;
         line-height: 50px;
+        display: flex;
 
         &:focus {
             outline: none;

+ 1 - 0
src/main.ts

@@ -12,6 +12,7 @@ import "@/styles/index.scss";
 import App from "@/App.vue";
 import store from "@/store";
 import router from "@/router/index";
+import '@/utils/directive' //自定义指令
 import "@/icons/components";
 import '@/assets/iconfont/iconfont.css'
 import "@/permission";

+ 19 - 0
src/router/index.ts

@@ -50,6 +50,25 @@ export default new Router({
       ]
     },
     {
+      path: "/allDetails",
+      component: Layout,
+      meta:{
+        hidden: true
+      },
+      children: [
+        {
+          path: "index",
+          component: () =>
+            import(
+              /* webpackChunkName: "project" */ "@/layout/components/Navbar/MessageSever/MsgAllDetails.vue"
+            ),
+          meta: {
+            title: "消息中心"
+          }
+        }
+      ]
+    },
+    {
       path: "/manage",
       component: Layout,
       meta: {

+ 7 - 0
src/store/modules/user.ts

@@ -15,6 +15,7 @@ export interface IUserState {
     username: string;
     userId: string;
     projectId: string;
+    projects: any[];
     avatar: string;
     introduction: string;
     roles: string[];
@@ -27,6 +28,7 @@ class User extends VuexModule implements IUserState {
     public username = "yanruolan";
     public userId = "06328a53c69a41bb8f5bb1a552c6e8d6";
     public projectId = "Pj4403070003";// 深圳龙岗万达广场
+    public projects: any[] = [];// 用户项目列表
     public avatar = "";
     public introduction = "";
     public roles: string[] = [];
@@ -47,6 +49,11 @@ class User extends VuexModule implements IUserState {
     }
 
     @Mutation
+    private SET_PROJECTS(projects: any[]) {
+        this.projects = projects;
+    }
+
+    @Mutation
     private SET_AVATAR(avatar: string) {
         this.avatar = avatar;
     }

+ 1 - 1
src/styles/_variables.scss

@@ -11,7 +11,7 @@ $yellow:#FEC171;
 $panGreen: #30B08F;
 
 // Sidebar
-$sideBarWidth: 210px;
+$sideBarWidth: 240px;
 $subMenuBg:#1f2d3d;
 $subMenuHover:#001528;
 $subMenuActiveText:#f4f4f5;

+ 26 - 0
src/utils/directive.ts

@@ -0,0 +1,26 @@
+import Vue from 'vue'
+//点击空白处隐藏div指令
+Vue.directive('clickOutside', {
+    // 初始化指令
+    bind(el: any, binding) {
+        function documentHandler(e: Event) {
+            // 这里判断点击的元素是否是本身,是本身,则返回
+            if (el.contains(e.target)) {
+                return false;
+            }
+            // 判断指令中是否绑定了函数
+            if (binding.expression) {
+                // 如果绑定了函数 则调用那个函数,此处binding.value就是handleClose方法
+                binding.value(e);
+            }
+        }
+        // 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
+        el.__vueClickOutside__ = documentHandler;
+        document.addEventListener('click', documentHandler);
+    },
+    unbind(el: any) {
+        // 解除事件监听
+        document.removeEventListener('click', el.__vueClickOutside__);
+        delete el.__vueClickOutside__;
+    },
+})

+ 1 - 1
tsconfig.json

@@ -40,7 +40,7 @@
         "src/**/*.vue",
         "tests/**/*.ts",
         "tests/**/*.tsx"
-    ],
+, "src/utils/directive.js"    ],
     "exclude": [
         "node_modules",
         "src/@types/**/*"

+ 5 - 1
vue.config.js

@@ -29,12 +29,16 @@ module.exports = {
                 changeOrigin: true
             },
             "/modelapi": {
-                target: "http://39.102.40.239:8080/",
+                target: "http://39.102.40.239:8080",
                 changeOrigin: true,
                 pathRewrite: {
                     "^/modelapi": "/revit-algorithm"
                 }
             },
+            "/message-center": {
+                target: "http://39.102.40.239:8080",
+                changeOrigin: true
+            },
             "/image-service": {
                 target: "http://39.97.179.199:8891",
                 changeOrigin: true