lex 2 years ago
parent
commit
b73fa09e5b
74 changed files with 6440 additions and 352 deletions
  1. 4 0
      package.json
  2. 55 12
      src/layout/components/AppMain.vue
  3. 134 0
      src/layout/components/components/class-list.vue
  4. 133 0
      src/layout/components/components/contacts-list.vue
  5. 118 0
      src/layout/components/components/contextmenu.vue
  6. 445 0
      src/layout/components/components/conversation-list.vue
  7. 216 0
      src/layout/components/components/message-editor.vue
  8. 4 0
      src/layout/components/components/message-factory/HQVoice-message.js
  9. 13 0
      src/layout/components/components/message-factory/base.js
  10. 107 0
      src/layout/components/components/message-factory/file-message.js
  11. 9 0
      src/layout/components/components/message-factory/gif-message.js
  12. 20 0
      src/layout/components/components/message-factory/image-message.js
  13. 48 0
      src/layout/components/components/message-factory/index.js
  14. 13 0
      src/layout/components/components/message-factory/location-message.js
  15. 45 0
      src/layout/components/components/message-factory/recall-message.js
  16. 90 0
      src/layout/components/components/message-factory/reference-message.js
  17. 24 0
      src/layout/components/components/message-factory/rich-message.js
  18. 40 0
      src/layout/components/components/message-factory/sight-message.js
  19. 11 0
      src/layout/components/components/message-factory/text-message.js
  20. 294 0
      src/layout/components/components/message-item.vue
  21. 578 0
      src/layout/components/components/message-list.vue
  22. BIN
      src/layout/components/images/group_logo.png
  23. BIN
      src/layout/components/images/student_logo.png
  24. BIN
      src/layout/components/images/teacher_logo.png
  25. 430 0
      src/layout/components/imkit/conversation.js
  26. 970 0
      src/layout/components/imkit/core.js
  27. 28 0
      src/layout/components/imkit/enum/event.js
  28. 6 0
      src/layout/components/imkit/enum/languages.js
  29. 20 0
      src/layout/components/imkit/enum/logEnums.js
  30. 12 0
      src/layout/components/imkit/images/close-btn.svg
  31. 14 0
      src/layout/components/imkit/images/download-icon.svg
  32. 13 0
      src/layout/components/imkit/images/file-icon-hover.svg
  33. 13 0
      src/layout/components/imkit/images/file-icon.svg
  34. BIN
      src/layout/components/imkit/images/ic_group_list_mussic_team_stu.png
  35. BIN
      src/layout/components/imkit/images/ic_group_list_vip.png
  36. BIN
      src/layout/components/imkit/images/icon_repertoire_play.png
  37. BIN
      src/layout/components/imkit/images/icon_training.png
  38. 14 0
      src/layout/components/imkit/images/image-icon-hover.svg
  39. 14 0
      src/layout/components/imkit/images/image-icon.svg
  40. 28 0
      src/layout/components/imkit/images/loading.svg
  41. 7 0
      src/layout/components/imkit/images/message-icon-apk.svg
  42. 12 0
      src/layout/components/imkit/images/message-icon-audio.svg
  43. 12 0
      src/layout/components/imkit/images/message-icon-image.svg
  44. 8 0
      src/layout/components/imkit/images/message-icon-pdf.svg
  45. 19 0
      src/layout/components/imkit/images/message-icon-plain.svg
  46. 12 0
      src/layout/components/imkit/images/message-icon-ppt.svg
  47. 17 0
      src/layout/components/imkit/images/message-icon-text.svg
  48. 15 0
      src/layout/components/imkit/images/message-icon-unknown.svg
  49. 12 0
      src/layout/components/imkit/images/message-icon-video.svg
  50. 14 0
      src/layout/components/imkit/images/message-icon-word.svg
  51. 12 0
      src/layout/components/imkit/images/message-icon-xls.svg
  52. 22 0
      src/layout/components/imkit/images/message-icon-zip.svg
  53. 15 0
      src/layout/components/imkit/images/no-conversation.svg
  54. 12 0
      src/layout/components/imkit/images/notification-disable.svg
  55. 16 0
      src/layout/components/imkit/images/send-fail.svg
  56. 16 0
      src/layout/components/imkit/images/sending.svg
  57. 14 0
      src/layout/components/imkit/images/sight-playing.svg
  58. 6 0
      src/layout/components/imkit/index.js
  59. 382 0
      src/layout/components/imkit/message.js
  60. 76 0
      src/layout/components/imkit/service.js
  61. 71 0
      src/layout/components/imkit/storage.js
  62. 28 0
      src/layout/components/imkit/utils.js
  63. 50 0
      src/layout/components/imkit/utils/bscroll-hack.js
  64. 50 0
      src/layout/components/imkit/utils/mouse-wheel-hack.js
  65. 195 0
      src/layout/components/imkit/utils/utils.js
  66. 64 0
      src/layout/components/imkit/zh_CN.js
  67. 382 0
      src/layout/components/message.js
  68. 84 0
      src/layout/components/modal/api.js
  69. 133 0
      src/layout/components/modal/chat-model-ui.vue
  70. 206 0
      src/layout/components/modal/chat-model.vue
  71. 121 0
      src/layout/components/modal/chat.js
  72. 105 92
      src/main.js
  73. 186 153
      src/store/modules/user.js
  74. 103 95
      vue.config.js

+ 4 - 0
package.json

@@ -15,6 +15,10 @@
   },
   "dependencies": {
     "@babel/plugin-proposal-optional-chaining": "^7.11.0",
+    "@rongcloud/engine": "^5.4.3",
+    "@rongcloud/imkit": "^5.3.0",
+    "@rongcloud/imlib-next": "^5.4.3",
+    "@vant/touch-emulator": "^1.4.0",
     "axios": "0.18.1",
     "browserslist": "^4.18.1",
     "caniuse-lite": "^1.0.30001286",

+ 55 - 12
src/layout/components/AppMain.vue

@@ -63,6 +63,23 @@
     >
       操作手册
     </div>
+
+    <svg
+      t="1659606821949"
+      class="icon optionMessage"
+      viewBox="0 0 1024 1024"
+      version="1.1"
+      xmlns="http://www.w3.org/2000/svg"
+      p-id="3078"
+      width="48"
+      height="48"
+      @click="chatVisible = true"
+    >
+      <path
+        d="M512 796a456.672 456.672 0 0 1-74.768-6.4L262 896V718.976C170.64 654.832 112 556.48 112 446 112 252.704 291.088 96 512 96s400 156.704 400 350S732.912 796 512 796z m0-650c-193.296 0-350 134.32-350 300 0 101.664 59.2 191.344 149.376 245.6l-1.6 115.968 117.68-70.736a404.8 404.8 0 0 0 84.544 9.168c193.296 0 350-134.304 350-300S705.296 146 512 146zM336 400a48 48 0 1 1-48 48 48 48 0 0 1 48-48z m176 0a48 48 0 1 1-48 48 48 48 0 0 1 48-48z m176 0a48 48 0 1 1-48 48 48 48 0 0 1 48-48z"
+        p-id="3079"
+      ></path>
+    </svg>
     <el-drawer
       title="操作手册"
       :visible.sync="outOptionvisible"
@@ -129,6 +146,16 @@
         </el-tab-pane>
       </el-tabs>
     </el-drawer>
+
+    <el-drawer
+      :append-to-body="true"
+      :visible.sync="chatVisible"
+      size="910px"
+      custom-class="innerDrawer"
+      :withHeader="false"
+    >
+      <chat-model @close="chatVisible = false" />
+    </el-drawer>
   </section>
 </template>
 
@@ -137,9 +164,10 @@ import notKeepAliveList from "@/router/notKeepAliveList";
 import serviceRemind from "@/components/serviceRemind"; // 续费弹窗
 import { permission } from "@/utils/directivePage";
 import { guideList } from "@/constant/guide";
+import chatModel from "./modal/chat-model";
 import {
   getSysManualList,
-  getSysManualMenuIds,
+  getSysManualMenuIds
 } from "@/views/operationManual/api";
 import "quill/dist/quill.core.css";
 import "quill/dist/quill.snow.css";
@@ -159,9 +187,10 @@ export default {
       allIdList: [],
       isShow: false,
       activeImg: null,
+      chatVisible: false
     };
   },
-  components: { serviceRemind },
+  components: { serviceRemind, chatModel },
   computed: {
     key() {
       return this.$route.path;
@@ -177,12 +206,12 @@ export default {
     },
     isShowBtn() {
       return this.allIdList.indexOf(this.$route.meta.id + "") != -1;
-    },
+    }
   },
   async mounted() {
-    this.$bus.$on("showguide", (obj) => {
+    this.$bus.$on("showguide", obj => {
       this.guideList = [];
-      obj.forEach((element) => {
+      obj.forEach(element => {
         if (guideList[element]) {
           this.guideList.push(guideList[element]);
         }
@@ -224,7 +253,7 @@ export default {
           menuId,
           page: 1,
           rows: 9999,
-          search: this.form.search,
+          search: this.form.search
         });
         this.optionList = res.data.rows;
 
@@ -237,8 +266,8 @@ export default {
       console.log(row);
       this.activeRow = row;
       this.innerDrawer = true;
-      this.$nextTick((res) => {
-        Array.from(document.querySelectorAll("img")).forEach((img) => {
+      this.$nextTick(res => {
+        Array.from(document.querySelectorAll("img")).forEach(img => {
           img.addEventListener("click", this.previewImg, true);
         });
       });
@@ -250,15 +279,15 @@ export default {
       let imgSrc = data.target.getAttribute("src");
       this.activeImg = imgSrc;
       this.$refs.previewImg.clickHandler();
-    },
+    }
   },
   watch: {
     innerDrawer(val) {
       if (!val) {
         this.activeName = "first";
       }
-    },
-  },
+    }
+  }
 };
 </script>
 <style lang="scss" scoped>
@@ -313,6 +342,20 @@ export default {
   cursor: pointer;
   z-index: 3999;
 }
+
+.optionMessage {
+  cursor: pointer;
+  position: fixed;
+  right: 36px;
+  bottom: 36px;
+  padding: 6px;
+  width: 46px;
+  height: 46px;
+  box-sizing: border-box;
+  border-radius: 50%;
+  background-color: #fff;
+  border: 1px solid #dcdfe6;
+}
 /deep/.line {
   height: 40px;
   line-height: 40px;
@@ -387,7 +430,7 @@ export default {
     padding-top: 15px;
     padding-bottom: 0;
     .el-dialog__headerbtn {
-      top:28px;
+      top: 28px;
       .el-icon {
         color: #999;
         font-size: 20px;

+ 134 - 0
src/layout/components/components/class-list.vue

@@ -0,0 +1,134 @@
+<template>
+  <div class="class-list">
+    <van-list
+      v-model="loading"
+      :finished="finished"
+      finished-text="没有更多了"
+      :immediate-check="false"
+      @load="onLoad"
+    >
+      <div
+        v-for="item in list"
+        :key="item.targetId"
+        class=" van-hairline--bottom"
+      >
+        <van-cell
+          title-class="class-content"
+          class="class-item"
+          center
+          @click="selectHandler(item)"
+        >
+          <template #icon>
+            <el-image
+              fit="cover"
+              :src="item.img || groupLogo"
+              @error="
+                () => {
+                  // 图片加载失败,显示默认头像
+                  item.img = groupLogo;
+                }
+              "
+              class="user-img"
+            />
+          </template>
+          <template #title>
+            <div class="username">
+              <div class="title">
+                {{ item.name }}
+              </div>
+              <div class="title-time"></div>
+            </div>
+            <div class="username">
+              <div class="desc">
+                {{ item.tags }}(共{{ item.memberNum || 0 }}人)
+              </div>
+            </div>
+          </template>
+        </van-cell>
+      </div>
+    </van-list>
+  </div>
+</template>
+
+<script>
+import { queryGroupList } from "../modal/api";
+import groupLogo from "../images/group_logo.png";
+import teacherLogo from "../images/teacher_logo.png";
+export default {
+  name: "class-list",
+  data() {
+    return {
+      groupLogo,
+      teacherLogo,
+      list: [],
+      loading: false,
+      finished: false
+    };
+  },
+  mounted() {
+    this.onLoad();
+  },
+  methods: {
+    async onLoad() {
+      try {
+        this.loading = true;
+        await queryGroupList().then(res => {
+          console.log(res, "res");
+          this.list = res.data;
+          this.loading = false;
+          this.finished = true;
+        });
+      } catch {}
+    },
+    selectHandler(item) {
+      this.$emit("select", item);
+    }
+  }
+};
+</script>
+
+<style lang="less" scoped>
+.class-item {
+  cursor: pointer;
+  &.isHover,
+  &:hover {
+    background: #f5f5f5;
+  }
+}
+.class-content {
+  padding-left: 10px;
+  .username {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+  .title-time {
+    font-size: 12px;
+    color: #999;
+  }
+  .title {
+    font-size: 15px;
+    font-weight: 600;
+    max-width: 170px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .desc {
+    font-size: 12px;
+    max-width: 170px;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    color: #999;
+  }
+
+  .isTop {
+    position: absolute;
+    top: 0;
+    left: 0;
+    border-top: 16px solid #00a79d;
+    border-right: 16px solid transparent;
+  }
+}
+</style>

+ 133 - 0
src/layout/components/components/contacts-list.vue

@@ -0,0 +1,133 @@
+<template>
+  <div class="contact-list">
+    <van-list
+      v-model="loading"
+      :finished="finished"
+      finished-text="没有更多了"
+      :immediate-check="false"
+      @load="onLoad"
+    >
+      <div
+        v-for="item in list"
+        :key="item.targetId"
+        class=" van-hairline--bottom"
+      >
+        <van-cell
+          title-class="contact-content"
+          class="contact-item"
+          center
+          @click="selectHandler(item)"
+        >
+          <template #icon>
+            <el-image
+              fit="cover"
+              :src="item.friend.avatar || teacherLogo"
+              @error="
+                () => {
+                  // 图片加载失败,显示默认头像
+                  item.friend.avatar = teacherLogo;
+                }
+              "
+              class="user-img"
+            />
+          </template>
+          <template #title>
+            <div class="username">
+              <div class="title">
+                {{ item.friendNickname }}
+              </div>
+              <div class="title-time"></div>
+            </div>
+            <div class="username">
+              <div class="desc">
+                {{ item.tags }}
+              </div>
+            </div>
+          </template>
+        </van-cell>
+      </div>
+    </van-list>
+  </div>
+</template>
+
+<script>
+import { queryFriendList } from "../modal/api";
+import groupLogo from "../images/group_logo.png";
+import teacherLogo from "../images/teacher_logo.png";
+export default {
+  name: "contact-list",
+  data() {
+    return {
+      groupLogo,
+      teacherLogo,
+      list: [],
+      loading: false,
+      finished: false
+    };
+  },
+  mounted() {
+    this.onLoad();
+  },
+  methods: {
+    async onLoad() {
+      try {
+        this.loading = true;
+        await queryFriendList().then(res => {
+          this.list = res.data;
+          this.loading = false;
+          this.finished = true;
+        });
+      } catch {}
+    },
+    selectHandler(item) {
+      this.$emit("select", item);
+    }
+  }
+};
+</script>
+
+<style lang="less" scoped>
+.contact-item {
+  cursor: pointer;
+  &.isHover,
+  &:hover {
+    background: #f5f5f5;
+  }
+}
+.contact-content {
+  padding-left: 10px;
+  .username {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+  .title-time {
+    font-size: 12px;
+    color: #999;
+  }
+  .title {
+    font-size: 15px;
+    font-weight: 600;
+    max-width: 120px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .desc {
+    font-size: 12px;
+    max-width: 145px;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    color: #999;
+  }
+
+  .isTop {
+    position: absolute;
+    top: 0;
+    left: 0;
+    border-top: 16px solid #00a79d;
+    border-right: 16px solid transparent;
+  }
+}
+</style>

+ 118 - 0
src/layout/components/components/contextmenu.vue

@@ -0,0 +1,118 @@
+<template>
+  <transition name="el-zoom-in-top">
+    <div
+      aria-hidden="true"
+      class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu"
+      role="tooltip"
+      data-popper-placement="bottom"
+      :style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
+      :key="Math.random()"
+      v-show="isShow"
+    >
+      <ul class="el-dropdown-menu">
+        <template v-for="(v, k) in dropdownList">
+          <li
+            class="el-dropdown-menu__item"
+            aria-disabled="false"
+            tabindex="-1"
+            :key="k"
+            @click="onCurrentContextmenuClick(v.contextMenuClickId)"
+          >
+            <span>{{ v.txt }}</span>
+          </li>
+        </template>
+      </ul>
+      <div class="el-popper__arrow" :style="{ left: `${arrowLeft}px` }"></div>
+    </div>
+  </transition>
+</template>
+
+<script>
+export default {
+  name: "contextmenu",
+  props: {
+    dropdown: {
+      type: Object,
+      default: () => {
+        return {
+          x: 0,
+          y: 0
+        };
+      }
+    },
+    dropdownList: {
+      type: Array,
+      default: []
+    }
+  },
+  data() {
+    return {
+      isShow: false,
+      item: {},
+      arrowLeft: 10
+    };
+  },
+  computed: {
+    dropdowns() {
+      // 117 为 `Dropdown 下拉菜单` 的宽度
+      if (this.dropdown.y + 130 > document.documentElement.clientHeight) {
+        return {
+          y: document.documentElement.clientHeight - 130 - 5,
+          x: this.dropdown.x - 50
+        };
+      } else {
+        return {
+          x: this.dropdown.x - 50,
+          y: this.dropdown.y
+        };
+      }
+    }
+  },
+  mounted() {
+    document.body.addEventListener("click", this.closeContextmenu);
+  },
+  methods: {
+    // 当前项菜单点击
+    onCurrentContextmenuClick(contextMenuClickId) {
+      this.$emit(
+        "currentContextmenuClick",
+        Object.assign({}, { contextMenuClickId }, this.item)
+      );
+    },
+    // 打开右键菜单:判断是否固定,固定则不显示关闭按钮
+    openContextmenu(item) {
+      this.closeContextmenu();
+      setTimeout(() => {
+        this.item = item;
+        this.isShow = true;
+      }, 10);
+    },
+    // 关闭右键菜单
+    closeContextmenu() {
+      this.isShow = false;
+    }
+  },
+  destroyed() {
+    document.body.removeEventListener("click", this.closeContextmenu);
+  }
+};
+</script>
+
+<style scoped lang="scss">
+.custom-contextmenu {
+  transform-origin: center top;
+  z-index: 2190;
+  position: fixed;
+  .el-dropdown-menu__item {
+    font-size: 12px !important;
+    white-space: nowrap;
+    i {
+      font-size: 12px !important;
+    }
+    &:hover {
+      background-color: #f7f7f7;
+      color: #333 !important;
+    }
+  }
+}
+</style>

+ 445 - 0
src/layout/components/components/conversation-list.vue

@@ -0,0 +1,445 @@
+<template>
+  <div class="conversation-list">
+    <!-- <div class="item"></div> -->
+    <van-list
+      v-model="loading"
+      :finished="finished"
+      finished-text="没有更多了"
+      :immediate-check="false"
+      @load="onLoad"
+    >
+      <div
+        v-for="item in list"
+        :key="item.targetId"
+        @contextmenu.prevent="onContextmenu(item, $event)"
+        class=" van-hairline--bottom"
+      >
+        <van-cell
+          title-class="message-content"
+          class="message-item"
+          :class="{ isHover: isHover(item.conversation) }"
+          center
+          @click="selectHandler(item)"
+        >
+          <template #icon>
+            <el-badge
+              :value="item.conversation.unreadMessageCount || 0"
+              class="item"
+              :hidden="(item.conversation.unreadMessageCount || 0) <= 0"
+            >
+              <el-image
+                :src="item.profile ? item.profile.portraitUri : ''"
+                class="user-img"
+              />
+            </el-badge>
+          </template>
+          <template #title>
+            <i class="isTop" v-if="item.conversation.isTop"></i>
+            <div class="username">
+              <div class="title">{{ item.profile.name }}</div>
+              <div class="title-time">
+                {{ formatTime(item.conversation.latestMessage.sentTime) }}
+              </div>
+            </div>
+            <div class="username">
+              <div class="desc">
+                {{ getLatestMessage(item.conversation) }}
+              </div>
+              <div v-if="item.conversation.notificationLevel === -1">
+                <img
+                  style="width: 18px"
+                  src="../imkit/images/notification-disable.svg"
+                />
+              </div>
+            </div>
+          </template>
+        </van-cell>
+      </div>
+    </van-list>
+
+    <Contextmenu
+      :dropdown="dropdown"
+      :dropdownList="dropdownList"
+      ref="contextmenuRef"
+      @currentContextmenuClick="onCurrentContextmenuClick"
+    />
+  </div>
+</template>
+
+<script>
+import Contextmenu from "./contextmenu.vue";
+import { imGroupQueryDetail } from "../modal/api";
+import groupLogo from "../images/group_logo.png";
+import teacherLogo from "../images/teacher_logo.png";
+import Vue from "vue";
+import { List, Cell } from "vant";
+Vue.use(List).use(Cell);
+import "vant/lib/index.css";
+import "@vant/touch-emulator"; // 引入模块后自动生效
+// import * as RongIMLib from "@rongcloud/imlib-next"; // 会话列表
+// import { MessageType } from "@rongcloud/engine"; // 消息类型
+import {
+  ConversationType,
+  getTextMessageDraft,
+  MessageDirection,
+  MessageType,
+  ReceivedStatus
+} from "@rongcloud/engine";
+import dayjs from "dayjs";
+const relativeTime = require("dayjs/plugin/relativeTime");
+const zhCN = require("dayjs/locale/zh-cn");
+dayjs.locale(zhCN);
+dayjs.extend(relativeTime);
+
+import { core, CoreEvent } from "../imkit";
+import locale from "../imkit/zh_CN";
+import { isEqual } from "../imkit/utils.js";
+import { deepClone } from "../imkit/utils/utils.js";
+
+export default {
+  components: {
+    Contextmenu
+  },
+  data() {
+    return {
+      conversationList: [],
+      list: [],
+      loading: false,
+      finished: false,
+      dropdown: { x: "", y: "" },
+      currentConversation: {},
+      dropdownList: [
+        {
+          contextMenuClickId: 0,
+          txt: "会话置顶"
+        },
+        { contextMenuClickId: 1, txt: "消息免打扰" },
+        {
+          contextMenuClickId: 2,
+          txt: "删除会话"
+        }
+      ]
+    };
+  },
+  mounted() {
+    // 会话列表更新
+    core.on(CoreEvent.CONVERSATION, this.getConversationList);
+    core.on(CoreEvent.UPDATE_CONVERSATION, this.handleUpdateConversation);
+    core.on(CoreEvent.SWITCH_CONVERSATION, this.switchConversation);
+    core.on(CoreEvent.CONNECTED, connectStatus => {
+      console.log("connectStatus", connectStatus);
+      if (connectStatus) {
+        this.getConversationList(true);
+        return;
+      }
+      this.list = [];
+    });
+    // 获取会话列表
+    // this.getConversationList(true);
+  },
+  methods: {
+    async onLoad() {
+      // 先显示正在加载,然后开始加载,保证下拉效果的顺滑
+      const beforeLength = JSON.parse(JSON.stringify(this.list)).length;
+      this.getConversationList(true).then(() => {
+        setTimeout(() => {
+          this.loading = false;
+
+          const length = this.list.length;
+          // 处理如果不能 % 不为0则加载完成,如果上一次加载数据和当前加载数据相等,则表示加载完成
+          if (20 % length || length === beforeLength) {
+            this.finished = true;
+          }
+        }, 200);
+      });
+    },
+    formatTime(val) {
+      return dayjs(val).fromNow();
+    },
+    selectHandler(item) {
+      console.log(item, "selectHandler");
+      core.selectConversation({
+        conversationType: item.conversation.conversationType,
+        targetId: item.conversation.targetId,
+        channelId: item.conversation.channelId
+      });
+      // 添加点击会话监听
+      core.emit("tapConversation", {
+        conversationType: item.conversation.conversationType,
+        targetId: item.conversation.targetId,
+        channelId: item.conversation.channelId
+      });
+    },
+    getConversationList(more = false) {
+      return core.getConversationList(more).then(list => {
+        // 处理没有名称的会话,没有名称说明在我们的服务器上没有数据
+        const tmpList = [];
+        const tmpMap = list || [];
+        tmpMap.forEach(item => {
+          if (item.profile && item.profile.name) {
+            tmpList.push(item);
+          }
+        });
+        this.list = [...tmpList];
+        // console.log(this.list, "data");
+      });
+    },
+    switchConversation(conversation) {
+      // console.log(conversation, "switchConversation");
+      this.currentConversation = conversation;
+    },
+    // 置顶会话
+    handleTopStatusChange(item) {
+      if (item === undefined) {
+        return;
+      }
+      core.setConversationToTop(
+        {
+          conversationType: item.profile.conversationType,
+          targetId: item.profile.targetId,
+          channelId: item.profile.channelId
+        },
+        !item.conversation.isTop
+      );
+    },
+    // 免打扰
+    handleNotificationStatusChange(item) {
+      if (item === undefined) {
+        return;
+      }
+
+      core.setConversationNotificationStatus(
+        {
+          conversationType: item.profile.conversationType,
+          targetId: item.profile.targetId,
+          channelId: item.profile.channelId
+        },
+        item.conversation.notificationLevel === -1 ? 0 : -1
+      );
+    },
+    // 删除会话
+    handleDelete(item) {
+      const conversation = {
+        conversationType: item.profile.conversationType,
+        targetId: item.profile.targetId,
+        channelId: item.profile.channelId || ""
+      };
+      core.deleteConversation(conversation).then(() => {
+        this.getConversationList(true);
+        // 删除会话后,清除当前会话
+        core.emit("deleteConversation", conversation);
+      });
+    },
+    /**
+     * 更新会话
+     * @param conversations
+     */
+    handleUpdateConversation(conversations = []) {
+      conversations.forEach(conversation => {
+        const index = this.list.findIndex(one => {
+          return one.conversation && isEqual(one.conversation, conversation);
+        });
+        if (index !== -1) {
+          this.list[index].conversation = conversation;
+        }
+      });
+      if (conversations.length) {
+        this.list = deepClone(this.list);
+      }
+    },
+    onContextmenu(item, e) {
+      // contextMenuClickId
+      if (item.conversation.notificationLevel === -1) {
+        this.dropdownList[1].txt = "取消免打扰";
+      } else {
+        this.dropdownList[1].txt = "消息免打扰";
+      }
+      if (item.conversation.isTop) {
+        this.dropdownList[0].txt = "取消置顶";
+      } else {
+        this.dropdownList[0].txt = "会话置顶";
+      }
+      const { clientX, clientY } = e;
+      this.dropdown.x = clientX;
+      this.dropdown.y = clientY;
+      this.$refs.contextmenuRef.openContextmenu(item);
+      // contextmenuRef.value.openContextmenu(v);
+    },
+    async onCurrentContextmenuClick(item) {
+      // console.log(item, "item");
+      switch (item.contextMenuClickId) {
+        case 0:
+          this.handleTopStatusChange(item);
+          break;
+        case 1:
+          this.handleNotificationStatusChange(item);
+          break;
+        case 2:
+          this.handleDelete(item);
+          break;
+      }
+    },
+    isHover(conversation) {
+      if (!this.currentConversation || !this.currentConversation.targetId) {
+        return false;
+      }
+      return (
+        conversation.channelId === this.currentConversation.channelId &&
+        conversation.targetId === this.currentConversation.targetId &&
+        conversation.conversationType ===
+          this.currentConversation.conversationType
+      );
+    },
+    getLatestMessage(conversation) {
+      const message = conversation.latestMessage;
+      if (!message) {
+        return "";
+      }
+      // let mentionedInfo = "";
+      // // 未读数不为 0,并且不是当前会话才展示 @ 信息
+      // let isShowAtMe =
+      //   conversation.unreadMessageCount != 0 &&
+      //   (!core.currentConversation ||
+      //     !isEqual(core.currentConversation, conversation));
+      // if (isShowAtMe) {
+      //   let atMe = "[" + locale.conversation.atMe + "]";
+      //   if (conversation.hasMentioned) {
+      //     mentionedInfo = atMe;
+      //   }
+      // }
+      let receivedStatus = "";
+      let styleClass = "";
+      // 发送状态判断
+      if (message.messageDirection === MessageDirection.SEND) {
+        if (message.messageUId.startsWith("fail_")) {
+          // 发送失败
+          styleClass = "fail";
+        } else if (message.messageUId.startsWith("sending_")) {
+          // 正在发送
+          styleClass = "sending";
+          //message.messageType !== 'polyfill' 为内存态生成会话消息类型需要过滤
+        } else if (
+          message.conversationType === ConversationType.PRIVATE &&
+          message.messageType !== "polyfill"
+        ) {
+          // 判断是否已读
+          if (message.receivedStatus === ReceivedStatus.UNREAD) {
+            // 暂时不显示已读未读状态
+            receivedStatus = "[" + locale.conversation.unread + "]";
+          } else {
+            // 暂时不显示已读未读状态
+            receivedStatus = "[" + locale.conversation.read + "]";
+          }
+        }
+      }
+      // console.log(message, "message");
+      let messageContent = "";
+      switch (message.messageType) {
+        case MessageType.TextMessage:
+          messageContent = message.content.content;
+          break;
+        case MessageType.FILE:
+          let file = "[" + locale.conversation.file + "]";
+          messageContent = file;
+          break;
+        case MessageType.IMAGE:
+          let img = "[" + locale.conversation.img + "]";
+          messageContent = img;
+          break;
+        case MessageType.LOCATION:
+          let location = "[" + locale.conversation.location + "]";
+          messageContent = location;
+          break;
+        case MessageType.SIGHT:
+          let video = "[" + locale.conversation.video + "]";
+          messageContent = video;
+          break;
+        case MessageType.HQ_VOICE:
+          let audio = "[" + locale.conversation.audio + "]";
+          messageContent = audio;
+          break;
+        case MessageType.REFERENCE:
+          let quote = "[" + locale.conversation.quote + "]";
+          messageContent = quote;
+          break;
+        case MessageType.VOICE:
+          let voice = "[" + locale.conversation.audio + "]";
+          messageContent = voice;
+          break;
+        default:
+          if (core.isCustomLastMessage(message)) {
+            messageContent = core.createCustomLastMessageDom(message)
+              ? core.createCustomLastMessageDom(message)
+              : "";
+          }
+      }
+      // 去掉换行格式
+      messageContent = messageContent.replace(/[\r\n]/g, "");
+      return receivedStatus + messageContent;
+    }
+  },
+  beforeDestroy() {
+    core.off(CoreEvent.CONVERSATION, this.getConversationList);
+    core.off(CoreEvent.UPDATE_CONVERSATION, this.handleUpdateConversation);
+    core.off(CoreEvent.SWITCH_CONVERSATION, this.switchConversation);
+  }
+};
+</script>
+
+<style lang="less">
+.user-img {
+  width: 56px;
+  height: 56px;
+  border-radius: 50%;
+  overflow: hidden;
+  flex-shrink: 0;
+  vertical-align: middle;
+}
+.item .is-fixed {
+  top: 3px !important;
+  right: 16px !important;
+}
+.message-item {
+  cursor: pointer;
+  &.isHover,
+  &:hover {
+    background: #f5f5f5;
+  }
+}
+.message-content {
+  padding-left: 10px;
+  .username {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+  .title-time {
+    font-size: 12px;
+    color: #999;
+  }
+  .title {
+    font-size: 15px;
+    font-weight: 600;
+    max-width: 120px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .desc {
+    font-size: 12px;
+    max-width: 145px;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    color: #999;
+  }
+
+  .isTop {
+    position: absolute;
+    top: 0;
+    left: 0;
+    border-top: 16px solid #00a79d;
+    border-right: 16px solid transparent;
+  }
+}
+</style>

+ 216 - 0
src/layout/components/components/message-editor.vue

@@ -0,0 +1,216 @@
+<template>
+  <div class="message-editor">
+    <div class="message-upload">
+      <i class="el-icon-picture-outline"></i>
+      <i class="el-icon-folder"></i>
+    </div>
+    <div class="message-input">
+      <textarea
+        class="message-input-text"
+        v-model="content"
+        rows="3"
+        @keyup.enter="handlePostMessage"
+      ></textarea>
+      <div class="message-input-btn">
+        <el-button type="primary" @click="handlePostMessage">发送</el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {
+  addEventListener,
+  removeEventListener,
+  clearTextMessageDraft,
+  ConversationType,
+  Events,
+  MentionedType,
+  ReferenceMessage,
+  TextMessage,
+  sendTypingStatusMessage,
+  MessageType
+} from "@rongcloud/imlib-next";
+import { delHtmlTag } from "../imkit/utils/utils";
+import { core, CoreEvent } from "../imkit";
+export default {
+  name: "message-editor",
+  data() {
+    return {
+      connected: true,
+      content: "",
+      conversation: {}
+    };
+  },
+  mounted() {
+    this.conversation = core.currentConversation;
+    console.log(core.currentConversation, "1212");
+    // 系统消息,不显示编辑器
+    if (
+      (core.currentConversation &&
+        core.currentConversation.conversationType) === ConversationType.SYSTEM
+    ) {
+      this.conversation = null;
+    }
+    core.on(CoreEvent.SWITCH_CONVERSATION, this.switchConversation);
+    core.on(CoreEvent.REFERENCE_MESSAGE, this.handleReferenceMessage);
+    core.on(CoreEvent.CLOSE_REFERENCE, this.handleCloseReference);
+
+    addEventListener(Events.CONNECTED, this.handleConnected);
+    addEventListener(Events.CONNECTING, this.handleConnecting);
+    addEventListener(Events.SUSPEND, this.handleSuspend);
+  },
+  methods: {
+    handlePostMessage(event) {
+      event.preventDefault();
+      console.log(this.conversation, "this.conversation");
+      let content = this.content;
+      if (!content) {
+        return;
+      }
+      // 判断是否提及了其他用户
+      const mentionedInfo = null;
+      // 将br标签替换为换行符
+      content = content.replace(/\<br\s?\/?\>/gi, "\n");
+      // 将nbsp;替换为空
+      content = content.replace(/&nbsp;/gi, " ");
+      content = delHtmlTag(content);
+      let messageBody;
+      let sendOption;
+      if (this.referenceMessage) {
+        messageBody = new ReferenceMessage({
+          referMsgUserId: this.referenceMessage.message.senderUserId,
+          referMsg: {
+            content: this.referenceMessage.message.content
+          },
+          content,
+          objName: this.referenceMessage.message.messageType
+        });
+      } else {
+        let messageInfo = {
+          content
+        };
+        sendOption = mentionedInfo
+          ? {
+              isMentioned: true,
+              ...mentionedInfo
+            }
+          : undefined;
+        if (!sendOption) {
+          messageInfo.mentionedInfo = sendOption;
+        }
+        messageBody = new TextMessage(messageInfo);
+      }
+      core.sendMessage(this.conversation, messageBody, sendOption);
+      this.content = "";
+      return false;
+    },
+    handleConnected() {
+      this.connected = true;
+    },
+    handleConnecting() {
+      this.connected = false;
+    },
+    handleSuspend() {
+      this.connected = false;
+    },
+    sendImageMessage() {
+      let typeArr = ["image/jpeg", "image/jpg", "image/png"];
+      if (!this.connected) {
+        return;
+      }
+      const files = this.imageInputRef.files;
+      if (!files.length) {
+        return;
+      }
+      const file = files[0];
+      if (typeArr.indexOf(files[0].type) === -1) {
+        this.isError = true;
+        setTimeout(() => {
+          this.isError = false;
+        }, 2000);
+        return;
+      }
+      core.sendImageMessage(
+        this.conversation,
+        { file },
+        {
+          onProgress: progress => {
+            console.log(progress);
+          },
+          onComplete: () => {}
+        }
+      );
+      this.imageInputRef.value = "";
+    },
+    sendFileMessage() {
+      if (!this.connected) {
+        return;
+      }
+      const files = this.fileInputRef.files;
+      if (!files.length) {
+        return;
+      }
+      const file = files[0];
+      core.sendFileMessage(
+        this.conversation,
+        { file },
+        {
+          onProgress: progress => {
+            console.log(progress);
+          },
+          onComplete: () => {}
+        }
+      );
+      this.fileInputRef.value = "";
+    }
+  },
+  destroyed() {
+    core.off(CoreEvent.SWITCH_CONVERSATION, this.switchConversation);
+    core.off(CoreEvent.REFERENCE_MESSAGE, this.handleReferenceMessage);
+    core.off(CoreEvent.CLOSE_REFERENCE, this.handleCloseReference);
+    core.off(CoreEvent.LANGUAGE_CHANGED, this.handleLanguageChanged);
+    removeEventListener(Events.CONNECTED, this.handleConnected);
+    removeEventListener(Events.CONNECTING, this.handleConnecting);
+    removeEventListener(Events.SUSPEND, this.handleSuspend);
+    if (this.content) {
+      core.saveTextMessageDraft(this.conversation, this.content);
+    }
+  }
+};
+</script>
+
+<style lang="less" scoped>
+.message-upload {
+  padding: 8px 0;
+  font-size: 22px;
+  color: #333;
+  i {
+    margin-right: 5px;
+    cursor: pointer;
+    &:hover {
+      color: #00a79d;
+    }
+  }
+}
+
+.message-input {
+  padding: 0 0 8px;
+  font-size: 22px;
+  color: #333;
+  textarea {
+    width: 100%;
+    height: 100%;
+    border: none;
+    outline: none;
+    resize: none;
+    font-size: 16px;
+    color: #333;
+  }
+  .message-input-btn {
+    display: flex;
+    justify-content: flex-end;
+    align-items: center;
+  }
+}
+</style>

File diff suppressed because it is too large
+ 4 - 0
src/layout/components/components/message-factory/HQVoice-message.js


+ 13 - 0
src/layout/components/components/message-factory/base.js

@@ -0,0 +1,13 @@
+export class ACreateMessage {
+  constructor(message, callback) {
+    this.uid = message.messageUId;
+    this.direction = message.messageDirection;
+    this.content = message.content;
+    this.callback = callback;
+    this.messageType = message.messageType;
+    this.conversationType = message.conversationType;
+    this.senderUserId = message.senderUserId;
+    this.targetId = message.targetId;
+    this.channelId = message.channelId;
+  }
+}

+ 107 - 0
src/layout/components/components/message-factory/file-message.js

@@ -0,0 +1,107 @@
+import { h } from "@stencil/core";
+import { ACreateMessage } from "./base";
+const fileTypeMap = {
+  'application/zip': 'file-zip',
+  'application/gzip': 'file-zip',
+  'application/x-zip-compressed': 'file-zip',
+  'application/x-tar': 'file-zip',
+  'application/x-compressed': 'file-zip',
+  'application/msword': 'file-word',
+  'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'file-word',
+  'application/rtf': 'file-text',
+  'application/vnd.ms-excel application/x-excel': 'file-xls',
+  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'file-xls',
+  'application/x-xls': 'file-xls',
+  'text/csv': 'file-xls',
+  'application/vnd.ms-excel': 'file-xls',
+  'application/x-ppt': 'file-ppt',
+  'application/vnd.ms-powerpoint': 'file-ppt',
+  'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'file-ppt',
+  'application/vnd.openxmlformats-officedocument.presentationml.slideshow': 'file-ppt',
+  'application/pdf': 'file-pdf',
+  'audio/mp3': 'file-audio',
+  'audio/wav': 'file-audio',
+  'audio/m3u': 'file-audio',
+  'audio/midi': 'file-audio',
+  'audio/flac': 'file-audio',
+  'audio/ape': 'file-audio',
+  'audio/x-ms-wma': 'file-audio',
+  'video/mpeg4': 'file-video',
+  'video/x-ms-wmv': 'file-video',
+  'video/avi': 'file-video',
+  'video/mpg': 'file-video',
+  'video/mp4': 'file-video',
+  'video/x-msvideo': 'file-video',
+  'video/x-flv': 'file-video',
+  'application/x-mpegURL': 'file-video',
+  'video/3gpp': 'file-video',
+  'text/plain': 'file-plain',
+  'image/jpeg': 'file-image',
+  'image/tiff': 'file-image',
+  'image/png': 'file-image',
+  'image/gif': 'file-image',
+  'application/vnd.android.package-archive': 'file-apk',
+  'application/xml': 'file-text',
+  'application/rdf+xml': 'file-text',
+  'application/rss+xml': 'file-text',
+  'application/soap+xml': 'file-text',
+  'application/atom+xml': 'file-text',
+  'application/xhtml+xml': 'file-text',
+  'text/html': 'file-text',
+  'text/xml': 'file-text',
+  'text/css': 'file-text'
+};
+/**
+ * 格式化文件大小为适合查看的单位
+ * @param size 单位字节
+ * @returns
+ */
+function formatSize(size) {
+  if (size < 1024) {
+    return size + 'B';
+  }
+  else if (size > 1024 * 1024 * 1024) {
+    size = (size / 1024 / 1024 / 1024).toFixed(2) + 'G';
+  }
+  else if (size > 1024 * 1024) {
+    size = (size / 1024 / 1024).toFixed(2) + 'M';
+  }
+  else if (size > 1024) {
+    size = (size / 1024).toFixed(2) + 'K';
+  }
+  return size;
+}
+export default class FileMessage extends ACreateMessage {
+  constructor(message, callback) {
+    super(message, callback);
+    this.handleClick = this.handleClick.bind(this);
+  }
+  handleClick() {
+    if (typeof this.callback === 'function') {
+      this.callback({
+        type: 'file',
+        url: this.content.fileUrl,
+        size: this.content.size,
+        name: this.content.name,
+        fileType: this.content.type
+      });
+    }
+  }
+  getFileIcon(type, name) {
+    const fileType = fileTypeMap[type];
+    if (!fileType) {
+      console.log('暂未识别的文件类型:' + type, name);
+    }
+    return fileType || 'file-icon';
+  }
+  create() {
+    const content = this.content;
+    return this.wrap(h("div", { class: "file-message-body", "data-origin": content.imageUri },
+      h("div", { class: 'file-message-icon ' + this.getFileIcon(content.type, content.name) }),
+      h("div", { class: "file-desc" },
+        h("div", { class: "file-name", title: content.name }, content.name),
+        h("div", { class: "file-size" }, formatSize(content.size))),
+      (this.uid.startsWith('sending_') || this.uid.startsWith('fail_')) ? null :
+        h("div", { class: "file-download-btn", onClick: this.handleClick })));
+  }
+}

+ 9 - 0
src/layout/components/components/message-factory/gif-message.js

@@ -0,0 +1,9 @@
+import { h } from "@stencil/core";
+import { ACreateMessage } from "./base";
+export default class GifMessage extends ACreateMessage {
+  create() {
+    const content = this.content;
+    return this.wrap(h("div", { class: "gif-message-body" },
+      h("img", { src: content.remoteUrl })));
+  }
+}

File diff suppressed because it is too large
+ 20 - 0
src/layout/components/components/message-factory/image-message.js


+ 48 - 0
src/layout/components/components/message-factory/index.js

@@ -0,0 +1,48 @@
+import { MessageType } from "@rongcloud/imlib-next";
+// import FileMessage from "./file-message";
+// import GifMessage from "./gif-message";
+// import HQVoiceMessage from "./HQVoice-message";
+import ImageMessage from "./image-message";
+// import LocationMessage from "./location-message";
+import RecallMessage from "./recall-message";
+// import ReferenceMessage from "./reference-message";
+// import RichMessage from "./rich-message";
+// import SightMessage from "./sight-message";
+import TextMessage from "./text-message";
+/**
+ * 通知类的消息类型
+ */
+export const ntfMessageTypeMap = {
+  // 只存储,不展示的消息
+  "RC:RcCmd": RecallMessage,
+  "RC:InfoNtf": null,
+  "RC:ContactNtf": null,
+  "RC:ProfileNtf": null,
+  "RC:CmdNtf": null,
+  "RC:GrpNtf": null
+};
+/**
+ * 常规的消息类型,用于展示
+ */
+export const commonMessageTypeMap = {
+  [MessageType.TEXT]: TextMessage,
+  [MessageType.IMAGE]: ImageMessage
+  // [MessageType.FILE]: FileMessage,
+  // [MessageType.SIGHT]: SightMessage,
+  // [MessageType.HQ_VOICE]: HQVoiceMessage,
+  // [MessageType.GIF]: GifMessage,
+  // [MessageType.REFERENCE]: ReferenceMessage,
+  // [MessageType.RICH_CONTENT]: RichMessage,
+  // [MessageType.LOCATION]: LocationMessage,
+  // ['RC:VcMsg']: HQVoiceMessage
+};
+function getMessageBody(message, callback) {
+  const func =
+    commonMessageTypeMap[message.messageType] ||
+    ntfMessageTypeMap[message.messageType];
+  if (!func) {
+    return "不识别的消息内容";
+  }
+  return new func(message, callback).create();
+}
+export default getMessageBody;

+ 13 - 0
src/layout/components/components/message-factory/location-message.js

@@ -0,0 +1,13 @@
+import { h } from "@stencil/core";
+import { ACreateMessage } from "./base";
+import language from "../../../../src/language/index";
+import { core } from "../../../core";
+export default class LocationMessage extends ACreateMessage {
+  create() {
+    let lang = core.lang ? core.lang : language.zh_CN;
+    let locale = language[lang];
+    let location = '[' + locale.message.location + ']' + locale.message.nonsupport;
+    return this.wrap(h("div", { class: "location-message-body" },
+      h("p", null, location)));
+  }
+}

+ 45 - 0
src/layout/components/components/message-factory/recall-message.js

@@ -0,0 +1,45 @@
+import { ConversationType, MessageDirection } from "@rongcloud/imlib-next";
+import { core } from "../../imkit";
+import zhCN from "../../imkit/zh_CN";
+export default class RecallMessage {
+  create() {
+    let name;
+    // let lang = core.lang ? core.lang : language.zh_CN;
+    let locale = zhCN;
+    if (this.direction === MessageDirection.SEND) {
+      name = locale.message.you + " ";
+    } else {
+      if (this.conversationType === ConversationType.PRIVATE) {
+        // name = locale.message.opposite+" ";
+        core
+          .getConversationProfile({
+            targetId: this.targetId,
+            conversationType: ConversationType.PRIVATE,
+            channelId: this.channelId
+          })
+          .then(res => {
+            name = res ? res.name : this.senderUserId;
+            this.nameRef.innerText = name + " ";
+          });
+      } else if (this.conversationType === ConversationType.GROUP) {
+        core
+          .getGroupMemberProfile(
+            {
+              targetId: this.targetId,
+              conversationType: ConversationType.GROUP,
+              channelId: this.channelId
+            },
+            this.senderUserId
+          )
+          .then(res => {
+            name = res
+              ? res.groupNickname || res.name || this.senderUserId
+              : this.senderUserId;
+            this.nameRef.innerText = name + " ";
+          });
+      }
+    }
+    console.log(locale.message.msgRecall, name, "name");
+    return name;
+  }
+}

+ 90 - 0
src/layout/components/components/message-factory/reference-message.js

@@ -0,0 +1,90 @@
+import { ConversationType, getCurrentUserId, MessageType } from "@rongcloud/imlib-next";
+import { h } from "@stencil/core";
+import { core } from "../../../core";
+import { addBase64head, formatTextMessage } from "../../../utils/utils";
+import { ACreateMessage } from "./base";
+import language from "../../../../src/language/index";
+export default class ReferenceMessage extends ACreateMessage {
+  constructor(message, callback) {
+    super(message, callback);
+    this.getName(message);
+  }
+  getName(message) {
+    this.name = this.content.referMsgUserId;
+    const conversationOption = {
+      conversationType: message.conversationType,
+      targetId: message.targetId,
+      channelId: message.channelId
+    };
+    if (message.conversationType === ConversationType.GROUP) {
+      core.getGroupMemberProfile(conversationOption, this.name).then(res => {
+        if (!res) {
+          return;
+        }
+        this.name = res.groupNickname || res.name;
+        if (this.nameRef) {
+          this.nameRef.innerText = this.name;
+        }
+      });
+    }
+    else if (message.conversationType === ConversationType.PRIVATE) {
+      let promise;
+      if (message.content.referMsgUserId === getCurrentUserId()) {
+        promise = core.getMyProfile();
+      }
+      else {
+        promise = core.getConversationProfile(conversationOption);
+      }
+      promise.then(res => {
+        if (!res) {
+          return;
+        }
+        this.name = res.displayName || res.name;
+        if (this.nameRef) {
+          this.nameRef.innerText = this.name;
+        }
+      });
+    }
+  }
+  getRererenceContent(type, referMsg) {
+    let content = "";
+    let handledContent = "";
+    let lang = core.lang ? core.lang : language.zh_CN;
+    let locale = language[lang];
+    switch (type) {
+      case MessageType.TEXT:
+        content = referMsg.content.content;
+        handledContent = formatTextMessage(content);
+        content = (h("div", { innerHTML: handledContent, class: "referenced-message-text" },
+          h("div", { class: "quote quoteContent", innerHTML: handledContent })));
+        break;
+      case MessageType.IMAGE:
+        content = h("img", { src: addBase64head(referMsg.content.content) });
+        break;
+      case MessageType.GIF:
+        content = h("img", { src: referMsg.content.remoteUrl });
+        break;
+      case MessageType.FILE:
+        // content = `[文件] ${referMsg.content.name}`;
+        content = '[' + locale.conversation.file + ']' + ` ${referMsg.content.name}`;
+        break;
+      case MessageType.RICH_CONTENT:
+        // content = `[图文] ${referMsg.content.title}`;
+        content = '[' + locale.conversation.img + ']' + ` ${referMsg.content.title}`;
+        break;
+      case MessageType.REFERENCE:
+        // content = "[引用消息]" + (referMsg.content as any).content;
+        content = referMsg.content.content;
+        break;
+    }
+    return content;
+  }
+  create() {
+    const content = this.content;
+    return this.wrap(h("div", { class: "reference-message-body" },
+      h("div", { class: "reference-message-content" },
+        h("div", { class: "user", ref: el => (this.nameRef = el) }, this.name),
+        h("div", { class: "content" }, this.getRererenceContent(content.objName, content.referMsg))),
+      h("div", { class: "message-content", innerHTML: formatTextMessage(content.content) })));
+  }
+}

+ 24 - 0
src/layout/components/components/message-factory/rich-message.js

@@ -0,0 +1,24 @@
+import { h } from "@stencil/core";
+import { ACreateMessage } from "./base";
+export default class RichMessage extends ACreateMessage {
+  constructor(message, callback) {
+    super(message, callback);
+    this.handleClick = this.handleClick.bind(this);
+  }
+  handleClick() {
+    if (typeof this.callback === 'function') {
+      this.callback({
+        type: 'richContent',
+        url: this.content.url
+      });
+    }
+  }
+  create() {
+    const content = this.content;
+    return this.wrap(h("div", { class: "rich-message-body", "data-url": content.url, onClick: this.handleClick },
+      h("div", { class: "title" }, content.title),
+      h("div", { class: "content" },
+        content.imageUri ? h("img", { src: content.imageUri }) : null,
+        h("p", null, content.content))));
+  }
+}

+ 40 - 0
src/layout/components/components/message-factory/sight-message.js

@@ -0,0 +1,40 @@
+import { h } from "@stencil/core";
+import { addBase64head } from "../../../utils/utils";
+import { ACreateMessage } from "./base";
+export default class SightMessage extends ACreateMessage {
+  constructor(message, callback) {
+    super(message, callback);
+    this.handleClick = this.handleClick.bind(this);
+  }
+  handleClick() {
+    if (typeof this.callback === 'function') {
+      this.callback({
+        type: 'sight',
+        url: this.content.sightUrl,
+        thumb: this.content.content,
+        duration: this.content.duration,
+        size: this.content.size,
+        name: this.content.name
+      });
+    }
+  }
+  formatDuration(duration) {
+    // const minute = duration % 60;
+    // const second = (duration / 60).toFixed(0);
+    // return `${second}:${minute}`;
+    const hour = Math.floor(duration / 3600) >= 10 ? Math.floor(duration / 3600) : '0' + Math.floor(duration / 3600);
+    duration -= 3600 * hour;
+    let min = Math.floor(duration / 60) >= 10 ? Math.floor(duration / 60) : '0' + Math.floor(duration / 60);
+    duration -= 60 * min;
+    let sec = duration >= 10 ? duration : '0' + duration;
+    return hour * 1 > 0 ? hour + ':' + min + ':' + sec : min + ':' + sec;
+  }
+  create() {
+    const content = this.content;
+    return this.wrap(h("div", { class: "sight-message-body", "data-origin": content.imageUri, onClick: this.handleClick },
+      h("img", { class: "sight-conver", src: addBase64head(content.content) }),
+      h("div", { class: "file-duration" }, this.formatDuration(content.duration)),
+      h("div", { class: "start-play-btn" },
+        h("button", null))));
+  }
+}

+ 11 - 0
src/layout/components/components/message-factory/text-message.js

@@ -0,0 +1,11 @@
+import { formatTextMessage } from "../../imkit/utils/utils";
+import { ACreateMessage } from "./base";
+export default class TextMessage extends ACreateMessage {
+  create() {
+    let content = this.content.content;
+    console.log("content", content);
+    content = formatTextMessage(content);
+
+    return content;
+  }
+}

File diff suppressed because it is too large
+ 294 - 0
src/layout/components/components/message-item.vue


+ 578 - 0
src/layout/components/components/message-list.vue

@@ -0,0 +1,578 @@
+<template>
+  <div class="message-list" ref="pullRefresh">
+    <div class="message-header">{{ className }}</div>
+
+    <van-pull-refresh
+      v-model="refreshing"
+      @refresh="onRefresh"
+      :disabled="pullingDisabled"
+      :pulling-text="'下拉加载更多...'"
+      :loosing-text="'释放即可加载...'"
+    >
+      <div v-for="item in messages" :key="item.sentTime">
+        <message-item
+          :message="item"
+          :conversation="conversation"
+          :conversationProfile="conversationProfile"
+          :groupMembers="groupMembers"
+          :myProfile="myProfile"
+          @onRightClick="rightClickHandler"
+          @onTap="tapMessageHandler"
+        />
+      </div>
+    </van-pull-refresh>
+  </div>
+</template>
+
+<script>
+import {
+  ConversationType,
+  getTextMessageDraft,
+  MessageDirection,
+  MessageType,
+  ReceivedStatus
+} from "@rongcloud/engine";
+import {
+  sendReadReceiptMessage,
+  sendReadReceiptResponseV2
+} from "@rongcloud/imlib-next";
+import MessageItem from "./message-item";
+import { isEqual } from "../imkit/utils.js";
+import { core, CoreEvent } from "../imkit";
+import Vue from "vue";
+import { List, PullRefresh, Cell } from "vant";
+Vue.use(List)
+  .use(PullRefresh)
+  .use(Cell);
+import "vant/lib/index.css";
+import "@vant/touch-emulator"; // 引入模块后自动生效
+export default {
+  name: "message-list",
+  components: {
+    MessageItem
+  },
+  data() {
+    return {
+      isConnect: false, // 是否连接
+      hasMore: true, // 是否有更多消息
+      className: "",
+      messages: [],
+      refreshing: false,
+      pullingDisabled: false,
+      conversation: {},
+      conversationProfile: {},
+      groupMembers: [],
+      myProfile: {},
+      isGetHistory: false, //是否为拉取历史消息
+      lastPosition: 0 // 下拉时记录当前的滚动位置
+    };
+  },
+  mounted() {
+    core.on(CoreEvent.SWITCH_CONVERSATION, this.handleSwitchConversation);
+    core.on(CoreEvent.MESSAGES, this.handleMessages);
+    core.on(CoreEvent.MESSAGE_RECEIPT_REQUEST, this.handleReceiptRequest);
+    core.on(CoreEvent.UPDATE_MESSAGE, this.handleUpdateMessage);
+    core.on(CoreEvent.DELETE_MESSAGE, this.handleDeleteMessage);
+    core.on(CoreEvent.MESSAGE_SEND_SUCCESS, this.handleUpdateMessage);
+    core.on(CoreEvent.MESSAGE_SEND_FAIL, this.handleUpdateMessage);
+    core.on(CoreEvent.TYPING_STATUS, this.handleTypingStatus);
+
+    // 登陆成功-更新用户Profile
+    core.on(CoreEvent.CONNECTED, connectStatus => {
+      this.isConnect = connectStatus;
+      if (this.isConnect) {
+        this.updateMyProfile();
+        return;
+      }
+      this.myProfile = null;
+    });
+
+    console.log(core.currentConversation, "core.currentConversation");
+    if (core.currentConversation) {
+      this.handleSwitchConversation(core.currentConversation);
+    }
+  },
+  methods: {
+    getMessageStyleClass(message) {
+      const styleMap = {
+        [MessageType.IMAGE]: "image-message-item",
+        [MessageType.SIGHT]: "sight-message-item",
+        [MessageType.GIF]: "gif-message-item",
+        [MessageType.FILE]: "file-message-item"
+      };
+      return (styleMap[message.messageType] || message.messageType) + " ";
+    },
+    rightClickHandler(message, e) {
+      if (e.button !== 2) {
+        return;
+      }
+      e.preventDefault();
+      return this.rightClick.emit({
+        message,
+        position: { top: e.clientY, left: e.clientX }
+      });
+    },
+    getReceivedStatus(message, locale) {
+      let receivedStatus = "";
+      if (
+        message.messageDirection === MessageDirection.SEND &&
+        message.conversationType === ConversationType.PRIVATE
+      ) {
+        // 判断是否已读
+        if (message.receivedStatus === ReceivedStatus.UNREAD) {
+          // 暂时不显示已读未读状态
+          // receivedStatus = "未读";
+          receivedStatus = locale.conversation.unread;
+        } else {
+          // 暂时不显示已读未读状态
+          // receivedStatus = "已读";
+          receivedStatus = locale.conversation.read;
+        }
+      }
+      return receivedStatus;
+    },
+    getProgressBar(message) {
+      return message.messageUId.indexOf("sending") === 0 &&
+        message.messageId &&
+        [
+          MessageType.IMAGE,
+          MessageType.FILE,
+          MessageType.SIGHT,
+          MessageType.HQ_VOICE,
+          MessageType.GIF
+        ].indexOf(message.messageType) !== -1
+        ? h("progress-bar", {
+            mId: message.messageId,
+            reverse: message.messageType === MessageType.FILE
+          })
+        : null;
+    },
+    handleClick(data) {
+      this.tap.emit(data);
+    },
+    handleFail() {
+      const conversation = {
+        conversationType: this.message.conversationType,
+        targetId: this.message.targetId,
+        channelId: this.message.channelId
+      };
+      core.resendMessage(conversation, this.message);
+    },
+    onRefresh() {
+      // 清空列表数据
+      this.finished = false;
+
+      // 重新加载数据
+      // 将 loading 设置为 true,表示处于加载状态
+      this.loading = true;
+
+      // 记录当前的位置
+      this.lastPosition = document.querySelector("#el-main").scrollTop;
+      // 没有更多消息,不再处理下拉
+      if (!this.hasMore) {
+        this.refreshing = false;
+        this.pullingDisabled = true;
+        return;
+      }
+      let lastestTime = Date.now();
+      if (this.messages.length) {
+        lastestTime = this.messages[0].sentTime;
+      }
+      // 先显示正在加载,然后开始请求数据,保证显示效果
+
+      if (this.isConnect) {
+        // 获取历史消息
+        core
+          .getMessages(
+            {
+              conversationType: this.conversation.conversationType,
+              targetId: this.conversation.targetId,
+              channelId: this.conversation.channelId
+            },
+            lastestTime,
+            30
+          )
+          .then(messages => {
+            // 判断是否有数据
+            console.log(messages, "messages");
+            if (!messages.list.length) {
+              this.hasMore = messages.hasMore;
+              this.refreshing = false;
+              this.pullingDisabled = true;
+              // this.scrollToBottom(this.lastPosition);
+              return;
+            }
+            // 判断返回的数据是否是当前会话的
+            if (
+              messages.list[0].conversationType !==
+                this.conversation.conversationType ||
+              messages.list[0].targetId !== this.conversation.targetId ||
+              messages.list[0].channelId !== this.conversation.channelId
+            ) {
+              this.refreshing = false;
+              // this.pullingDisabled = true;
+              // this.scrollToBottom(this.lastPosition);
+              return;
+            }
+            setTimeout(() => {
+              // 顺序,方便显示渲染
+              console.log(messages.list, "1212");
+              this.messages = messages.list.concat(this.messages);
+              this.hasMore = messages.hasMore;
+
+              this.refreshing = false;
+              // this.scrollToBottom(this.lastPosition);
+              this.isGetHistory = true;
+            }, 600);
+          });
+      }
+    },
+    /**
+     * 切换conversation
+     * @param option 会话
+     */
+    handleSwitchConversation(option) {
+      if (!option) {
+        this.conversation = null;
+        return;
+      }
+      // 切换会话清除掉上一回话历史消息【防止服务器异常时没有办法正常加载历史消息展示错乱】
+      if (this.messages) {
+        this.messages = [];
+      }
+      this.updateMessageList(option);
+    },
+    /**
+     * 加载数据
+     * @param data
+     */
+    loadData(data) {
+      console.log(data, "loadData");
+      this.className = data.profile.name;
+      this.conversation = data.conversation;
+      this.conversationProfile = data.profile;
+      if (this.conversation.conversationType === ConversationType.GROUP) {
+        console.log("join group");
+        core.getGroupMembers(this.conversation).then(res => {
+          console.log(res, "getGroupMembers");
+          this.groupMembers = [...res];
+          this.getMessages();
+        });
+      } else {
+        this.groupMembers = null;
+        this.getMessages();
+      }
+    },
+    /**
+     * 获取消息
+     * @returns
+     */
+    getMessages() {
+      this.isLoading = true;
+      return core
+        .getMessages({
+          conversationType: this.conversation.conversationType,
+          targetId: this.conversation.targetId,
+          channelId: this.conversation.channelId
+        })
+        .then(messages => {
+          this.isLoading = false;
+          this.messages = messages.list;
+          this.hasMore = messages.hasMore;
+          console.log(this.messages);
+          if (this.messages.length) {
+            this.scrollToBottom();
+            //   if (!this.bscroll) {
+            //     setTimeout(() => {
+            //       // 创建过程中包含滚动到最后一条位置,因此不需要再往下执行
+            //       this.mountScroller();
+            //     }, 100);
+            //   } else {
+            //     // 滚动到第一条的位置
+            //     setTimeout(() => {
+            //       this.bscroll.refresh();
+            //       this.bscroll.scrollTo(0, this.bscroll.maxScrollY);
+            //     }, 600);
+            //   }
+          }
+          // 存在未读消息
+          if (this.conversation.unreadMessageCount) {
+            let lastMessages = this.messages[this.messages.length - 1];
+            // let lastMessages;
+            // if (this.messages && this.messages.length) {
+            //   lastMessages = this.findUnreadMessages(this.messages);
+            // } else if (this.conversation.latestMessage) {
+            //   lastMessages = [this.conversation.latestMessage];
+            // }
+            if (lastMessages) {
+              this.sendReadReceipt(lastMessages);
+            }
+          }
+
+          console.log(this.messages);
+        });
+    },
+    scrollToBottom(time = 200, scrollY = 0) {
+      this.$nextTick(() => {
+        setTimeout(() => {
+          const pullRefresh = this.$refs.pullRefresh;
+          const scrollY = scrollY || pullRefresh.offsetHeight;
+          console.log(scrollY, "scrollY");
+          document.querySelector("#el-main").scrollTo(0, scrollY);
+        }, time);
+      });
+    },
+    /**
+     * 处理新消息
+     */
+    handleMessages(newMessages) {
+      console.log(newMessages, "newMessages");
+      if (!newMessages.length || !this.conversation) {
+        return;
+      }
+      // if (
+      //   newMessages[0].conversationType !== this.conversation.conversationType ||
+      //   newMessages[0].targetId !== this.conversation.targetId ||
+      //   newMessages[0].channelId !== this.conversation.channelId
+      // ) {
+      //   return;
+      // }
+      if (!isEqual(newMessages[0], this.conversation)) {
+        return;
+      }
+      const refreshData = () => {
+        const messages = [...this.messages, ...newMessages];
+        this.messages = messages;
+        console.log(this.messages, "this.messages");
+
+        this.scrollToBottom(100);
+        // if (!this.bscroll) {
+        //   this.mountScroller();
+        // }
+        // 在一定范围内,则不滚动到底部
+        // if (this.bscroll.y < this.bscroll.maxScrollY + 30) {
+        //   // TODO 待优化,为临时修复切换标签当前页面收到消息刷新异常问题调整时间戳从 100 调整到 1000。
+        //   setTimeout(() => {
+        //     this.bscroll.refresh();
+        //     this.bscroll.scrollTo(0, this.bscroll.maxScrollY);
+        //   }, 100);
+        // } else {
+        //   setTimeout(() => {
+        //     this.bscroll.refresh();
+        //   }, 100);
+        // }
+        // const unreadMessages = this.findUnreadMessages(messages);
+        const unreadMessage = messages[messages.length - 1];
+        if (unreadMessage) {
+          // 发送已读回执
+          this.sendReadReceipt(unreadMessage);
+        }
+      };
+      // 如果是群组,则先要获取群组用户
+      if (this.conversation.conversationType === ConversationType.GROUP) {
+        core
+          .getGroupMembers({
+            conversationType: this.conversation.conversationType,
+            targetId: this.conversation.targetId,
+            channelId: this.conversation.channelId
+          })
+          .then(res => {
+            this.groupMembers = res;
+            refreshData();
+          });
+      } else {
+        this.groupMembers = null;
+        refreshData();
+      }
+    },
+    /**
+     * 找到收到的最后一条数据
+     */
+    // private findLastReceivedMessage(messages: IAReceivedMessage[]): IAReceivedMessage | undefined {
+    //   let lastMessage: IAReceivedMessage;
+    //   for (let i = messages.length - 1; i >= 0; i--) {
+    //     if (messages[i].messageDirection === MessageDirection.RECEIVE) {
+    //       lastMessage = messages[i];
+    //       break;
+    //     }
+    //   }
+    //   return lastMessage;
+    // }
+    // private findUnreadMessages(
+    //   messages: IAReceivedMessage[]
+    // ): IAReceivedMessage[] {
+    //   return messages.filter(message => {
+    //     return (
+    //       message.messageDirection === MessageDirection.RECEIVE &&
+    //       message.receivedStatus === ReceivedStatus.UNREAD
+    //     );
+    //   });
+    // }
+    /**
+     * 发送已读回执
+     */
+    sendReadReceipt(unreadMessages) {
+      core.clearMessagesUnreadStatus(this.conversation);
+      if (!unreadMessages) {
+        return;
+      }
+      // unreadMessages.forEach(message => {
+      //   if (message.conversationType === ConversationType.PRIVATE) {
+      //     // 发送已读回执
+      //     sendReadReceiptMessage(
+      //       this.conversation.targetId,
+      //       message.messageUId,
+      //       core.getTime(),
+      //       this.conversation.channelId
+      //     );
+      //   }
+      // });
+      if (unreadMessages.conversationType === ConversationType.PRIVATE) {
+        // 发送已读回执
+        sendReadReceiptMessage(
+          this.conversation.targetId,
+          unreadMessages.messageUId,
+          unreadMessages.sentTime,
+          // core.getTime(),
+          this.conversation.channelId
+        );
+      }
+    },
+    /**
+     * 处理群回执请求
+     */
+    handleReceiptRequest(event) {
+      // 直接发送响应
+      sendReadReceiptResponseV2(
+        event.conversation.targetId,
+        { [event.senderUserId]: [event.messageUId] },
+        event.conversation.channelId
+      );
+    },
+    handleSelectMessage(event) {
+      const { message, position } = event.detail;
+      if (
+        message.messageUId.startsWith("sending_") ||
+        message.messageUId.startsWith("fail_")
+      ) {
+        return;
+      }
+      this.selectedMessage = message;
+      this.contextMenuPosition = position;
+    },
+    handleUpdateMessage(message) {
+      const index = this.messages.findIndex(
+        m => message.messageUId === m.messageUId
+      );
+      if (index === -1) {
+        return;
+      }
+      this.messages[index] = message;
+      this.messages = [...this.messages];
+      this.scrollToBottom(100);
+      // if (!this.bscroll) {
+      //   this.mountScroller();
+      //   return;
+      // }
+      // setTimeout(() => {
+      //   this.bscroll.refresh();
+      // }, 100);
+    },
+    // 删除消息
+    handleDeleteMessage(messages) {
+      messages.forEach(message => {
+        const index = this.messages.findIndex(
+          m => message.messageUId === m.messageUId
+        );
+        if (index === -1) {
+          return;
+        }
+        this.messages.splice(index, 1);
+      });
+      this.messages = [...this.messages];
+      this.scrollToBottom(100);
+      // if (!this.bscroll) {
+      //   this.mountScroller();
+      //   return;
+      // }
+      // setTimeout(() => {
+      //   this.bscroll.refresh();
+      // }, 100);
+    },
+    tapMessageHandler(data) {
+      console.log(data);
+      // this.tapMessage.emit(data.detail);
+    },
+    handleMouseLeave() {
+      if (this.bscroll) {
+        this.bscroll.stop();
+        // 鼠标按住左键移出滚动区域,放开左键后,回到滚动区域依然带动滚动动作。
+        this.bscroll.scroller.actionsHandler.setInitiated();
+      }
+    },
+    handleTypingStatus(event) {
+      const status = event.status || [];
+      const hasCurrentConversation = status.findIndex(one => {
+        return this.conversation && isEqual(one, this.conversation);
+      });
+      if (hasCurrentConversation === -1) {
+        return;
+      }
+      if (this.typingTimer) {
+        clearTimeout(this.typingTimer);
+        this.typingTimer = null;
+      }
+      this.showTypingStatus = true;
+      this.typingTimer = setTimeout(() => {
+        this.showTypingStatus = false;
+      }, 5000);
+    },
+    updateMyProfile() {
+      core.getMyProfile().then(res => {
+        this.myProfile = res;
+        console.log(res, "updateMyProfile");
+      });
+    },
+    updateMessageList(option) {
+      const currentConversation = option || core.currentConversation;
+      if (currentConversation && currentConversation !== this.conversation) {
+        core.getConversation(currentConversation).then(res => {
+          if (res) {
+            this.loadData(res);
+          }
+        });
+      }
+    }
+  },
+  destroyed() {
+    core.off(CoreEvent.SWITCH_CONVERSATION, this.handleSwitchConversation);
+    core.off(CoreEvent.MESSAGES, this.handleMessages);
+    core.off(CoreEvent.MESSAGE_RECEIPT_REQUEST, this.handleReceiptRequest);
+    core.off(CoreEvent.UPDATE_MESSAGE, this.handleUpdateMessage);
+    core.off(CoreEvent.DELETE_MESSAGE, this.handleDeleteMessage);
+    core.off(CoreEvent.MESSAGE_SEND_SUCCESS, this.handleUpdateMessage);
+    core.off(CoreEvent.MESSAGE_SEND_FAIL, this.handleUpdateMessage);
+    core.off(CoreEvent.TYPING_STATUS, this.handleTypingStatus);
+  }
+};
+</script>
+
+<style lang="less" scoped>
+.message-list {
+  // height: 100%;
+}
+.message-header {
+  position: sticky;
+  top: 0;
+  font-size: 16px;
+  padding: 12px 20px;
+  color: #333;
+  font-weight: 500;
+  z-index: 1;
+  background: #fff;
+}
+
+/deep/ .van-list {
+  min-height: 280px;
+}
+</style>

BIN
src/layout/components/images/group_logo.png


BIN
src/layout/components/images/student_logo.png


BIN
src/layout/components/images/teacher_logo.png


+ 430 - 0
src/layout/components/imkit/conversation.js

@@ -0,0 +1,430 @@
+import {
+  ConversationType,
+  logger,
+  MessageDirection,
+  ReceivedStatus
+} from "@rongcloud/engine";
+import {
+  setConversationNotificationStatus,
+  setConversationNotificationLevel,
+  setConversationToTop,
+  removeConversation,
+  getConversationList
+} from "@rongcloud/imlib-next";
+import { LogTagId } from "./enum/logEnums";
+// 填充的空会话标识
+const PolyfillMessage = "polyfill";
+export default class ConversationManager {
+  constructor() {
+    // 第一次拉取数量
+    this.FIRST_COUNT_BASE = 30;
+    // 每次的拉取数量
+    this.COUNT_BASE = 30;
+    // 是否为第一次拉取
+    this.first = true;
+    /**
+     * 会话分为置顶会话和非置顶会话,
+     */
+    this.topConversations = [];
+    this.unTopConversations = [];
+  }
+  /**
+   * 设置拉取数量
+   * @param option
+   */
+  setPullCount(option) {
+    this.COUNT_BASE = option.pullCount;
+    this.FIRST_COUNT_BASE = option.pullCount;
+  }
+  /**
+   * 获取远程会话
+   * @param count
+   * @param startTime
+   * @param order
+   * @returns Promise<void | IAsyncRes<IAReceivedConversation[]>>
+   */
+  getRemoteConversation(count, startTime = 0, conversationStorage, order = 0) {
+    return getConversationList({
+      count,
+      startTime,
+      order
+    }).then(result => {
+      if (result.code === 0) {
+        let data = result.data;
+        data.forEach(conversation => {
+          const unReadTime = conversationStorage.getUnReadTime(
+            conversation.conversationType,
+            conversation.targetId,
+            conversation.channelId
+          );
+          // 根据本地存储的已读响应时间戳判断单聊消息的已读未读状态。
+          const isPrivat =
+            conversation.latestMessage.conversationType ===
+            ConversationType.PRIVATE;
+          const isSendUser =
+            conversation.latestMessage.messageDirection ===
+            MessageDirection.SEND;
+          // 大于本地已读存储时间或者没有本地存储已读时间均设置为未读
+          const isUnRead =
+            conversation.latestMessage.sentTime > unReadTime || !unReadTime;
+          if (isPrivat && isSendUser && isUnRead) {
+            // 单聊,自己发送的消息
+            conversation.latestMessage.receivedStatus = ReceivedStatus.UNREAD;
+            // console.info('修改未读', conversation)
+          }
+        });
+        // 排除用户需要自己接管的数据
+        if (
+          this.customIntercept &&
+          typeof this.customIntercept.interceptConversation === "function"
+        ) {
+          data = data.filter(one => {
+            const isMatch = this.customIntercept.interceptConversation(one);
+            return !isMatch;
+          });
+        }
+        // 用户处理lastMessage
+        if (
+          this.customDisplayMessage &&
+          this.customDisplayMessage.willDisplayConversationMessage &&
+          typeof this.customDisplayMessage.willDisplayConversationMessage ===
+            "function"
+        ) {
+          data = data.map(one => {
+            let lastMessage = one.latestMessage;
+            if (lastMessage) {
+              let content = this.customDisplayMessage.willDisplayConversationMessage(
+                lastMessage.content,
+                lastMessage.conversationType,
+                lastMessage.targetId,
+                lastMessage.messageType
+              );
+              one.latestMessage.content = content;
+            }
+            return one;
+          });
+        }
+        this.update(
+          data.map(one => {
+            return { conversation: one };
+          })
+        );
+      }
+    });
+  }
+  /**
+   * 获取一个会话
+   * @param target
+   * @param source
+   * @returns number
+   */
+  findConversationFromList(target, source) {
+    const index = source.findIndex(one => {
+      return (
+        one.conversationType === target.conversationType &&
+        one.targetId === target.targetId &&
+        one.channelId === target.channelId
+      );
+    });
+    return index;
+  }
+  /**
+   * 在本地缓存数据中找一个会话
+   * @param target 目标会话参数
+   * @returns IAReceivedConversation 会话
+   */
+  findConversation(target) {
+    target.channelId = target.channelId || "";
+    let index = this.findConversationFromList(target, this.topConversations);
+    if (index !== -1) {
+      return {
+        source: this.topConversations,
+        index
+      };
+    }
+    index = this.findConversationFromList(target, this.unTopConversations);
+    if (index !== -1) {
+      return {
+        source: this.unTopConversations,
+        index
+      };
+    }
+    return null;
+  }
+  /**
+   * 接受SDK返回的会话数据
+   * @param conversationList
+   */
+  update(conversationList) {
+    // 找到空会话
+    const polyfillTopConversation = this.topConversations.filter(one => {
+      return (
+        one.latestMessage && one.latestMessage.messageType === PolyfillMessage
+      );
+    });
+    const polyfillUnTopConversation = this.unTopConversations.filter(one => {
+      return (
+        one.latestMessage && one.latestMessage.messageType === PolyfillMessage
+      );
+    });
+    const polyfillConversation = polyfillTopConversation.concat(
+      polyfillUnTopConversation
+    );
+    conversationList = conversationList.concat(
+      polyfillConversation.map(one => {
+        return {
+          conversation: one
+        };
+      })
+    );
+    conversationList.forEach(one => {
+      if (!one || (!one.conversation && !one.updatedItems)) {
+        return;
+      }
+      // 找到会话
+      const matchResult = this.findConversation({
+        conversationType: one.conversation.conversationType,
+        targetId: one.conversation.targetId,
+        channelId: one.conversation.channelId || "" // channelId 可能会是undefined
+      });
+      let target;
+      target = matchResult
+        ? matchResult.source[matchResult.index]
+        : one.conversation;
+      // 新数据字段可能不完整,用新数据存在的字段更新老的会话数据
+      this.merge(target, one.conversation);
+      if (
+        one.updatedItems !== undefined &&
+        Object.keys(one.updatedItems).length
+      ) {
+        one.updatedItems.hasOwnProperty("isTop") &&
+          matchResult &&
+          matchResult.source.splice(matchResult.index, 1);
+        target &&
+          Object.keys(one.updatedItems).forEach(key => {
+            target[key] = one.updatedItems[key].val;
+          });
+      } else if (one.conversation) {
+        matchResult && matchResult.source.splice(matchResult.index, 1);
+      }
+      // logger.info(LogTagId.A_KIT_CONVERSATION_UPDATE_O, { target, conversationUpdate: one })
+      if (target.isTop) {
+        this.sortConversation(target, this.topConversations);
+      } else {
+        this.sortConversation(target, this.unTopConversations);
+      }
+    });
+  }
+  merge(target, source) {
+    for (const key in source) {
+      if (source[key] !== undefined && source[key] !== null) {
+        target[key] = source[key];
+      }
+    }
+  }
+  /**
+   * 会话排序,按照会话的最后一条消息
+   * @param source
+   * @param target
+   * @returns void
+   */
+  sortConversation(source, target) {
+    if (!target.length) {
+      target.push(source);
+    }
+    // 查看是否已经存在
+    const index = target.findIndex(conversation => {
+      return (
+        conversation.targetId === source.targetId &&
+        conversation.channelId === source.channelId &&
+        conversation.conversationType === source.conversationType
+      );
+    });
+    // 如果存在则先删除
+    if (index !== -1) {
+      if (target[index].latestMessage) {
+        if (source.latestMessage) {
+          if (
+            target[index].latestMessage.sentTime > source.latestMessage.sentTime
+          ) {
+            // 当前会话的最后一条消息时间大于新会话数据的消息时间
+            return;
+          }
+        } else {
+          // 新的会话数据没有最后一条消息
+          return;
+        }
+      }
+      // source = Object.assign({}, target[index], source)
+      target.splice(index, 1);
+    }
+    let position = -1;
+    for (let i = 0; i < target.length; i++) {
+      const conversation = target[i];
+      if (conversation.latestMessage) {
+        if (
+          source.latestMessage &&
+          source.latestMessage.sentTime >= conversation.latestMessage.sentTime
+        ) {
+          position = i;
+          break;
+        } else {
+          continue;
+        }
+      } else {
+        position = i;
+        break;
+      }
+    }
+    if (position === -1) {
+      position = target.length;
+    }
+    target.splice(position, 0, source);
+  }
+  /**
+   * 创建一个新会话,并插入到本地会话中
+   * @param option
+   */
+  create(option) {
+    const conversation = {
+      conversationType: option.conversationType,
+      channelId: option.channelId,
+      targetId: option.targetId,
+      latestMessage: {
+        conversationType: option.conversationType,
+        targetId: option.targetId,
+        channelId: option.channelId,
+        sentTime: new Date().getTime(),
+        messageType: PolyfillMessage,
+        content: "",
+        senderUserId: "",
+        receivedTime: 0,
+        messageUId: "none",
+        messageDirection: 1,
+        isPersited: true,
+        isCounted: true,
+        isOffLineMessage: false,
+        canIncludeExpansion: false,
+        receivedStatus: ReceivedStatus.UNREAD
+      }
+    };
+    this.sortConversation(conversation, this.unTopConversations);
+    logger.info(LogTagId.A_KIT_CONVERSATION_CREATE_O, conversation);
+  }
+  /**
+   * 移除一个会话
+   * @param conversation
+   * @returns void
+   */
+  remove(conversation) {
+    return removeConversation(conversation).then(res => {
+      const result = this.findConversation(conversation);
+      if (result) {
+        if (res.code === 0) {
+          result.source.splice(result.index, 1);
+          logger.info(LogTagId.A_KIT_CONVERSATION_DELETE_O, {
+            index: result.index,
+            conversation: result.source[result.index]
+          });
+        }
+      }
+      return res;
+    });
+  }
+  /**
+   * 获取所有会话中最新的消息发送时间
+   * @param conversationList
+   * @returns number
+   */
+  getLastTime(conversationList) {
+    let lastTime = new Date().getTime();
+    for (let i = conversationList.length - 1; i >= 0; i--) {
+      const conversation = conversationList[i];
+      if (conversation.latestMessage) {
+        lastTime = conversation.latestMessage.sentTime;
+        break;
+      }
+    }
+    return lastTime;
+  }
+  /**
+   * 获取会话列表
+   * @param more 为true的情况下从服务端拉取,如果是false则从本地获取
+   * @returns Promise<IAReceivedConversation[]>
+   */
+  getList(more, conversationStorage) {
+    if (more) {
+      // if (more || this.first) {
+      // 找到最后一条会话的sentTime
+      const topLastTime = this.getLastTime(this.topConversations);
+      const unTopLastTime = this.getLastTime(this.unTopConversations);
+      const count = this.first ? this.FIRST_COUNT_BASE : this.COUNT_BASE;
+      const lastTime = Math.min(topLastTime, unTopLastTime);
+      return this.getRemoteConversation(
+        count,
+        lastTime,
+        conversationStorage
+      ).then(() => {
+        this.first = false;
+        const list = [...this.topConversations, ...this.unTopConversations];
+        return list;
+      });
+    } else {
+      const list = [...this.topConversations, ...this.unTopConversations];
+      return Promise.resolve(list);
+    }
+  }
+  /**
+   * 改变会话置顶状态
+   * @param option
+   * @param status
+   * @returns
+   */
+  changeTopStatus(option, status) {
+    return setConversationToTop(option, status).then(res => {
+      if (res.code === 0) {
+        // 改变状态之后,不需要本地重置,sdk会触发conversation事件
+        // 会话已经被更新
+        // const result = this.findConversation(option);
+        // if (status) {
+        //   // 置顶操作
+        //   if (result.source === this.unTopConversations) {
+        //     const conversation:IAReceivedConversation = this.unTopConversations.splice(result.index, 1)[0];
+        //     conversation.isTop = true;
+        //     this.sortConversation(conversation, this.topConversations);
+        //   }
+        // } else {
+        //   // 取消置顶操作
+        //   if (result.source === this.topConversations) {
+        //     const conversation:IAReceivedConversation = this.topConversations.splice(result.index, 1)[0];
+        //     conversation.isTop = false;
+        //     this.sortConversation(conversation, this.unTopConversations)
+        //   }
+        // }
+      }
+      return res;
+    });
+  }
+  /**
+   * 改变通知状态
+   * @param option
+   * @param status
+   * @returns
+   */
+  changeNotificationStatus(option, status) {
+    return setConversationNotificationLevel(option, status).then(res => {
+      if (res.code === 0) {
+        const result = this.findConversation(option);
+        result.source[result.index].notificationLevel = status;
+      }
+      return res;
+    });
+  }
+  /**
+   * disconnect 置空会话信息
+   */
+  clearLocalConversation() {
+    this.unTopConversations = [];
+    this.topConversations = [];
+  }
+}

+ 970 - 0
src/layout/components/imkit/core.js

@@ -0,0 +1,970 @@
+import {
+  ConversationType,
+  EventEmitter,
+  MessageDirection,
+  MessageType,
+  ReceivedStatus,
+  notEmptyString,
+  logger
+} from "@rongcloud/engine";
+import {
+  init,
+  connect,
+  disconnect,
+  addEventListener,
+  Events,
+  sendReadReceiptResponseV2,
+  recallMessage,
+  deleteMessages,
+  getServerTime,
+  sendMessage,
+  getCurrentUserId,
+  sendImageMessage,
+  ImageMessage,
+  FileMessage,
+  sendFileMessage,
+  clearMessagesUnreadStatus,
+  TextMessage,
+  ReferenceMessage,
+  saveTextMessageDraft,
+  registerMessageType
+} from "@rongcloud/imlib-next";
+import Service from "./service";
+import ConversationManager from "./conversation";
+import MessageManager from "./message";
+import { ConversationStore, LangStore } from "./storage";
+import { CoreEvent } from "./enum/event";
+import { Languages } from "./enum/languages";
+import { LogTagId } from "./enum/logEnums";
+import { file2Base64, getConversationKey, isEqual } from "./utils";
+// 需要排除的消息
+function isExcludeMessage(messageType) {
+  return ["RC:Delivered"].indexOf(messageType) !== -1;
+}
+function createMessageId() {
+  return ~~(Math.random() * 0xffffff);
+}
+/**
+ * 是否初始化过
+ */
+let hasInit = false;
+class Core extends EventEmitter {
+  constructor() {
+    super();
+    /**
+     * 记录切换会话时的时间
+     */
+    this.switchTimes = {};
+    // 缓存已读回执
+    this.receiptRequstCache = [];
+    this._fileCache = {};
+    if (Core.INSTANCE) {
+      return Core.INSTANCE;
+    }
+    this.conversationManager = new ConversationManager();
+    this.messageManager = new MessageManager();
+    this._conversationStorage = new ConversationStore();
+    this._langStore = new LangStore();
+    Core.INSTANCE = this;
+  }
+  init(option) {
+    if (hasInit) {
+      logger.error(LogTagId.A_KIT_INIT_O, { msg: "禁止多次初始化!" });
+    }
+    if (!option.libOption || !notEmptyString(option.libOption.appkey)) {
+      logger.error(LogTagId.A_KIT_INIT_O, {
+        msg: "参数错误:libOption.appkey 为必传字段!",
+        data: option
+      });
+    }
+    this._appkey = option.libOption.appkey;
+    // 初始化 imlib
+    init(option.libOption);
+    option.conversationPullCount = option.conversationPullCount
+      ? option.conversationPullCount
+      : 30;
+    this.conversationManager.setPullCount({
+      pullCount: option.conversationPullCount
+    });
+    this.customMessage = option.customMessage;
+    this.service = new Service(option.service);
+    // 注册消息
+    this.setListener();
+    hasInit = true;
+    // 初始化完毕
+    this.emit(CoreEvent.INIT);
+  }
+  connect(token, reconnectKickEnable) {
+    return connect(
+      token,
+      reconnectKickEnable
+    );
+  }
+  disconnect() {
+    // 清除当前用户内存态数据
+    this.conversationManager.clearLocalConversation();
+    this.messageManager.clearLocalConversation();
+    this.service.clearMyProfileCache();
+    return disconnect();
+  }
+  getTime() {
+    if (!this.startLocalTime || this.startServerTime) {
+      this.startLocalTime = Date.now();
+      this.startServerTime = getServerTime();
+    }
+    return Date.now() - this.startLocalTime + this.startServerTime;
+  }
+  get currentConversation() {
+    return this._currentConversation;
+  }
+  set currentConversation(conversation) {
+    this._currentConversation = conversation;
+  }
+  setListener() {
+    addEventListener(Events.CONNECTED, () => {
+      this._currentUsreId = getCurrentUserId();
+      this._conversationStorage.init(this._appkey, this._currentUsreId);
+      this._langStore.init(this._appkey, this._currentUsreId);
+      this.emit(CoreEvent.CONNECTED, true);
+      this.emit(CoreEvent.LANGUAGE_CHANGED, { lang: this.lang });
+    });
+    addEventListener(Events.DISCONNECT, () => {
+      this.currentConversation = null;
+      this.emit(CoreEvent.SWITCH_CONVERSATION, this.currentConversation);
+      this.emit(CoreEvent.CONNECTED, false);
+    });
+    addEventListener(Events.CONVERSATION, event => {
+      logger.info(LogTagId.A_KIT_CONVERSATION_LISTENER_S, event);
+      let updatedConversationList = event.conversationList;
+      // 将用户要自己接管的会话排除掉
+      if (
+        this.customIntercept &&
+        typeof this.customIntercept.interceptConversation === "function"
+      ) {
+        updatedConversationList = updatedConversationList.filter(one => {
+          const isMatch = this.customIntercept.interceptConversation(
+            one.conversation
+          );
+          return !isMatch;
+        });
+      }
+      // 会话更新可能是由一些消息触发的
+      updatedConversationList = updatedConversationList.filter(one => {
+        if (
+          one.updatedItems &&
+          one.updatedItems.latestMessage &&
+          one.updatedItems.latestMessage.val
+        ) {
+          const messageType = one.updatedItems.latestMessage.val.messageType;
+          return !isExcludeMessage(messageType);
+        }
+        return true;
+      });
+      if (updatedConversationList.length) {
+        this.conversationManager.update(updatedConversationList);
+        this.emit(CoreEvent.CONVERSATION, updatedConversationList);
+      }
+    });
+    addEventListener(Events.MESSAGES, event => {
+      logger.info(LogTagId.A_KIT_MESSAGE_LISTENER_S, event);
+      let messages = event.messages;
+      // 收到的新消息读取状态在sdk中已经被置为已读,需要重置回未读
+      messages.forEach(message => {
+        if (message.conversationType === ConversationType.PRIVATE) {
+          if (message.receivedStatus === ReceivedStatus.READ) {
+            message.receivedStatus = ReceivedStatus.UNREAD;
+          }
+        }
+      });
+      // 将用户要接管的消息全部排除
+      if (
+        this.customIntercept &&
+        typeof this.customIntercept.interceptMessage === "function"
+      ) {
+        messages = messages.filter(message => {
+          const isMatch = this.customIntercept.interceptMessage(message);
+          return !isMatch;
+        });
+      }
+      // 用户处理消息【供客户对消息进行加解密操作】
+      if (
+        this.customDisplayMessage &&
+        this.customDisplayMessage.willDisplayMessages &&
+        typeof this.customDisplayMessage.willDisplayMessages === "function"
+      ) {
+        messages = messages.map(message => {
+          let content = this.customDisplayMessage.willDisplayMessages(
+            message.content,
+            message.conversationType,
+            message.targetId,
+            message.messageType
+          );
+          message.content = content;
+          return message;
+        });
+      }
+      messages = messages.filter(message => {
+        return !isExcludeMessage(message.messageType);
+      });
+      if (!messages.length) {
+        return;
+      }
+      const localMessages = this.messageManager.setLocalMessages(messages);
+      // 收到新消息后,需要判断这些消息中是否有对应当前选中会话的消息
+      if (this.currentConversation && localMessages.new.length) {
+        this.emit(CoreEvent.MESSAGES, localMessages.new);
+      }
+      if (localMessages.remove && localMessages.remove.length) {
+        this.emit(CoreEvent.DELETE_MESSAGE, localMessages.remove);
+      }
+    });
+    addEventListener(Events.PULL_OFFLINE_MESSAGE_FINISHED, () => {
+      this.emit(CoreEvent.PULL_OFFLINE_MESSAGE_FINISHED);
+    });
+    addEventListener(Events.TYPING_STATUS, event => {
+      this.emit(CoreEvent.TYPING_STATUS, event);
+    });
+    // 收到单聊已读回执
+    addEventListener(Events.READ_RECEIPT_RECEIVED, event => {
+      const { conversation, sentTime } = event;
+      const conversationResult = this.conversationManager.findConversation(
+        conversation
+      );
+      if (conversationResult) {
+        const targetConversation =
+          conversationResult.source[conversationResult.index];
+        // 判断是否为最后一条消息,更新会话的latestMessage
+        const latestMessage = targetConversation.latestMessage;
+        if (latestMessage.sentTime && latestMessage.sentTime === sentTime) {
+          this.conversationManager.update([
+            {
+              conversation: targetConversation,
+              updatedItems: {
+                latestMessage: {
+                  time: Date.now(),
+                  val: {
+                    ...latestMessage,
+                    receivedStatus: ReceivedStatus.READ
+                  }
+                }
+              }
+            }
+          ]);
+          // 抛出会话更新通知
+          this.emit(CoreEvent.CONVERSATION, targetConversation);
+        }
+      }
+      // 更新本地消息数据中的对应消息
+      const messages = this.messageManager.getLocalMessage(conversation, 0, 0);
+      const index = messages.list.findIndex(one => one.sentTime === sentTime);
+      if (index !== -1) {
+        // const message = {
+        //   ...messages.list[index],
+        // };
+        messages.list.forEach(message => {
+          message.receivedStatus = ReceivedStatus.READ;
+          this.messageManager.updateMessage(message);
+          this.emit(CoreEvent.UPDATE_MESSAGE, message);
+        });
+      }
+      // 更新单聊已读状态
+      this._conversationStorage.updateUnReadTime(
+        sentTime,
+        conversation.conversationType,
+        conversation.targetId,
+        conversation.channelId
+      );
+      this.emit(CoreEvent.READ_RECEIPT_RECEIVED, event);
+    });
+    // 收到群组已读回执请求
+    addEventListener(Events.MESSAGE_RECEIPT_REQUEST, event => {
+      const { conversation, messageUId } = event;
+      if (isEqual(this.currentConversation, conversation)) {
+        // 既是当前会话, 则直接抛出收到的数据通知UI端
+        this.emit(CoreEvent.MESSAGE_RECEIPT_REQUEST, event);
+      } else {
+        const key = getConversationKey(conversation);
+        if (this.switchTimes[key]) {
+          const messages = this.messageManager.getLocalMessage(
+            conversation,
+            0,
+            0
+          );
+          // 方案并不完美,在消息量较大的情况下,由于内存中存储的消息量较少,可能会造成查不到之前的消息
+          const index = messages.list.findIndex(message => {
+            return (
+              message.messageUId === messageUId &&
+              message.receivedTime < this.switchTimes[key]
+            );
+          });
+          if (index === -1) {
+            // 将其进行缓存,待切换会话时传给消息列表
+            this.receiptRequstCache.push(event);
+          } else {
+            // 表示已经读过这条消息,直接发送已读响应
+            sendReadReceiptResponseV2(
+              conversation.targetId,
+              {
+                [event.senderUserId]: [event.messageUId]
+              },
+              conversation.channelId
+            );
+          }
+        } else {
+          // 将其进行缓存,待切换会话时传给消息列表
+          this.receiptRequstCache.push(event);
+        }
+      }
+    });
+    // 收到群聊已读回执响应
+    addEventListener(Events.MESSAGE_RECEIPT_RESPONSE, event => {
+      this.emit(CoreEvent.MESSAGE_RECEIPT_RESPONSE, event);
+    });
+  }
+  createConversation(conversation) {
+    return this.conversationManager.create(conversation);
+  }
+  deleteConversation(conversation) {
+    this.currentConversation = null;
+    this.emit(CoreEvent.SWITCH_CONVERSATION, this.currentConversation);
+    return this.conversationManager.remove(conversation);
+  }
+  setConversationToTop(option, status) {
+    return this.conversationManager.changeTopStatus(option, status);
+  }
+  setConversationNotificationStatus(option, status) {
+    return this.conversationManager.changeNotificationStatus(option, status);
+  }
+  referenceMessage(message) {
+    this.emit(CoreEvent.REFERENCE_MESSAGE, message);
+  }
+  recallMessage(message) {
+    recallMessage(
+      {
+        conversationType: message.conversationType,
+        targetId: message.targetId,
+        channelId: message.channelId
+      },
+      {
+        messageUId: message.messageUId,
+        sentTime: message.sentTime
+      }
+    ).then(res => {
+      if (res.code !== 0) {
+        return;
+      }
+      const result = this.messageManager.recallMessage(message);
+      if (result) {
+        this.emit(CoreEvent.UPDATE_MESSAGE, result);
+      }
+    });
+  }
+  deleteMessage(message) {
+    deleteMessages(
+      {
+        conversationType: message.conversationType,
+        targetId: message.targetId,
+        channelId: message.channelId
+      },
+      [
+        {
+          messageUId: message.messageUId,
+          sentTime: message.sentTime,
+          messageDirection: message.messageDirection
+        }
+      ]
+    ).then(res => {
+      if (res.code !== 0) {
+        return;
+      }
+      const result = this.messageManager.deleteMessage(message);
+      if (result) {
+        this.emit(CoreEvent.DELETE_MESSAGE, [message]);
+      }
+    });
+  }
+  registerMessageType(
+    messageType,
+    isPersited,
+    isCounted,
+    prototypes,
+    isStatusMessage
+  ) {
+    return registerMessageType(
+      messageType,
+      isPersited,
+      isCounted,
+      prototypes,
+      isStatusMessage
+    );
+  }
+  getConversationProfile(conversationOption) {
+    return this.service
+      .getConversationProfile([conversationOption])
+      .then(res => {
+        if (!res || !res.length) {
+          return null;
+        }
+        return res[0];
+      });
+  }
+  getConversationList(more = false) {
+    return this.conversationManager
+      .getList(more, this._conversationStorage)
+      .then(conversationList => {
+        // 整理出每个会话的三个属性
+        const conversationOptions = conversationList.map(conversation => {
+          return {
+            conversationType: conversation.conversationType,
+            targetId: conversation.targetId,
+            channelId: conversation.channelId
+          };
+        });
+        return this.service
+          .getConversationProfile(conversationOptions)
+          .then(profileList => {
+            // 将会话列表和profile合并
+            return conversationList.map(conversation => {
+              return {
+                conversation,
+                profile: profileList.find(profile => {
+                  if (profile) {
+                    if (isEqual(profile, conversation)) {
+                      return true;
+                    }
+                  }
+                  return false;
+                })
+              };
+            });
+          })
+          .catch(() => {
+            return conversationList.map(conversation => {
+              return {
+                conversation
+              };
+            });
+          });
+      });
+  }
+  getMyProfile() {
+    const id = getCurrentUserId();
+    if (!id) {
+      throw new Error("Unable to get the current user ID.");
+    }
+    return this.service.getUserProfile(id);
+  }
+  /**
+   * 判断消息是否为用户类自定义消息
+   * @param message IAReceivedMessage
+   * @returns boolean
+   */
+  isCustomUserMessage(message) {
+    if (!this.customMessage) {
+      return false;
+    }
+    const customUserMessage = this.customMessage.userMessage;
+    return (
+      customUserMessage && customUserMessage[message.messageType] !== undefined
+    );
+  }
+  /**
+   * 生成自定义的用户类消息 message内容
+   * @param message
+   * @returns
+   */
+  createCustomUserMessageDom(message) {
+    console.log(message, "createCustomUserMessageDom");
+    if (!this.customMessage) {
+      return null;
+    }
+
+    const customUserMessage = this.customMessage.userMessage;
+    if (customUserMessage) {
+      return customUserMessage[message.messageType](message);
+    }
+    return null;
+  }
+  /**
+   * 判断消息是否为通知类自定义消息
+   * @param message IAReceivedMessage
+   * @returns boolean
+   */
+  isCustomNotifyMessage(message) {
+    if (!this.customMessage) {
+      return false;
+    }
+    const customNotifyMessage = this.customMessage.notifyMessage;
+    return (
+      customNotifyMessage &&
+      customNotifyMessage[message.messageType] !== undefined
+    );
+  }
+  /**
+   * 生成自定义的通知类消息的message内容
+   * @param message
+   * @returns
+   */
+  createCustomNotifyMessageDom(message) {
+    if (!this.customMessage) {
+      return null;
+    }
+    const customNotifyMessage = this.customMessage.notifyMessage;
+    if (customNotifyMessage) {
+      return customNotifyMessage[message.messageType](message);
+    }
+    return null;
+  }
+  /**
+   * 判断消息是传递会话 lastmessage 展示描述
+   * @param message IAReceivedMessage
+   * @returns boolean
+   */
+  isCustomLastMessage(message) {
+    if (!this.customMessage) {
+      return false;
+    }
+    const customUserMessage = this.customMessage.lastMessage;
+    return (
+      customUserMessage && customUserMessage[message.messageType] !== undefined
+    );
+  }
+  /**
+   * 生成自定义的通知类消息的message内容
+   * @param message
+   * @returns
+   */
+  createCustomLastMessageDom(message) {
+    if (!this.customMessage) {
+      return null;
+    }
+    const customNotifyMessage = this.customMessage.lastMessage;
+    if (customNotifyMessage) {
+      return customNotifyMessage[message.messageType](message);
+    }
+    return null;
+  }
+  clearMessagesUnreadStatus(conversation) {
+    clearMessagesUnreadStatus(conversation);
+    // 因为在开发过程中,发现清理未读数后,发现sdk并没有发出conversation事件,因此需要手动更新
+    this.conversationManager.update([
+      {
+        conversation,
+        updatedItems: {
+          unreadMessageCount: {
+            time: Date.now(),
+            val: 0
+          }
+        }
+      }
+    ]);
+    this.emit(CoreEvent.UPDATE_CONVERSATION, [
+      { ...conversation, unreadMessageCount: 0 }
+    ]);
+  }
+  saveTextMessageDraft(conversation, content) {
+    saveTextMessageDraft(conversation, content).then(res => {
+      // 保存成功
+      if (res.code === 0) {
+        this.emit(CoreEvent.SWITCH_CONVERSATION, this.currentConversation);
+      } else {
+        // 草稿保存失败
+        console.log(res.code, res.msg);
+      }
+    });
+  }
+  /**
+   *
+   * @param conversation 会话
+   * @param start 开始时间,默认从最新一条的时间开始
+   * @param count 获取数量,默认20条
+   * @returns
+   */
+  getMessages(conversation, startTime = 0, count = 20) {
+    const unReadTime = this._conversationStorage.getUnReadTime(
+      conversation.conversationType,
+      conversation.targetId,
+      conversation.channelId
+    );
+    return this.messageManager.getMessages(
+      conversation,
+      startTime,
+      count,
+      unReadTime
+    );
+  }
+  getGroupMemberProfile(conversationOption, userId) {
+    if (conversationOption.conversationType === ConversationType.GROUP) {
+      if (!userId) return null;
+      return this.service.getGroupMembers(conversationOption).then(members => {
+        const index = members.findIndex(member => member.id === userId);
+        if (index !== -1) {
+          return members[index];
+        }
+        return null;
+      });
+    }
+  }
+  getGroupMembers(conversation) {
+    return this.service.getGroupMembers(conversation);
+  }
+  selectConversation(conversation) {
+    console.log(conversation, "selectConversation");
+    console.log(this.currentConversation, "this.currentConversation");
+    if (
+      this.currentConversation &&
+      isEqual(this.currentConversation, conversation)
+    ) {
+      return;
+    }
+    // 如果会话不存在,则主动创建
+    if (!this.conversationManager.findConversation(conversation)) {
+      this.createConversation(conversation);
+      this.emit(CoreEvent.CONVERSATION, this.currentConversation);
+    }
+    // 存储当前切换的时间
+    if (this.currentConversation) {
+      const key = getConversationKey(this.currentConversation);
+      this.switchTimes[key] = this.getTime();
+      // 截取当前会话下消息列表的长度,只保留最新的100条
+      this.messageManager.cutMessages();
+    }
+    this.currentConversation = conversation;
+    this.messageManager.currentConversation = this.currentConversation;
+    this.emit(CoreEvent.SWITCH_CONVERSATION, this.currentConversation);
+    this.receiptRequstCache.forEach(one => {
+      // 切换会话的时候,直接将回执通知UI层处理
+      if (isEqual(one.conversation, this.currentConversation)) {
+        this.emit(CoreEvent.MESSAGE_RECEIPT_REQUEST, one);
+      }
+    });
+  }
+  getConversation(option) {
+    const result = this.conversationManager.findConversation(option);
+    if (result && result.index !== -1) {
+      return this.service.getConversationProfile([option]).then(profileList => {
+        const profile = profileList[0];
+        return { conversation: result.source[result.index], profile };
+      });
+    }
+    return Promise.resolve(null);
+  }
+  /**
+   * 发送文本消息
+   * 在各个阶段,会抛出不同的事件
+   * @param conversation
+   * @param message
+   * @param option
+   */
+  sendMessage(conversation, message, option) {
+    const messageId = createMessageId();
+    // const preMessage = Object.assign({},message);
+    const messageContent = message.content;
+    let preMessage = message;
+    // 发送的是引用消息,则抛出关闭引用消息窗口
+    if (message.messageType === MessageType.REFERENCE) {
+      this.emit(CoreEvent.CLOSE_REFERENCE);
+      preMessage = new ReferenceMessage(Object.assign({}, messageContent));
+    }
+    if (message.messageType === MessageType.TextMessage) {
+      preMessage = new TextMessage({ content: messageContent.content });
+    }
+    if (
+      message.messageType === MessageType.TextMessage ||
+      message.messageType === MessageType.REFERENCE
+    ) {
+      message.content = this._willSendMessage(
+        message.content,
+        conversation.conversationType,
+        conversation.targetId,
+        message.messageType
+      );
+    }
+    return this.decorateMessage(
+      conversation,
+      messageId,
+      preMessage,
+      sendMessage(conversation, message, option)
+    );
+  }
+  sendImageMessage(conversationOption, msgBody, hooks, options) {
+    // 使用闭包的方式保存messageId,方便消息回来后调用
+    const messageId = createMessageId();
+    this._fileCache[messageId] = msgBody;
+    const decorate = base64 => {
+      msgBody = this._willSendMessage(
+        msgBody,
+        conversationOption.conversationType,
+        conversationOption.targetId,
+        MessageType.IMAGE
+      );
+      return this.decorateMessage(
+        conversationOption,
+        messageId,
+        new ImageMessage({
+          content: base64,
+          imageUri: ""
+        }),
+        sendImageMessage(
+          conversationOption,
+          msgBody,
+          {
+            onProgress: progress => {
+              this.emit(CoreEvent.UPLOAD_PROGRESS, {
+                messageId,
+                progress
+              });
+              if (typeof hooks.onProgress === "function") {
+                hooks.onProgress(progress);
+              }
+            },
+            onComplete: fileInfo => {
+              this.emit(CoreEvent.UPLOAD_COMPLETE, {
+                messageId,
+                fileInfo
+              });
+              if (typeof hooks.onComplete === "function") {
+                hooks.onComplete(fileInfo);
+              }
+            }
+          },
+          options
+        )
+      );
+    };
+    return file2Base64(msgBody.file)
+      .then(base64 => {
+        decorate(base64);
+      })
+      .catch(() => {
+        decorate("");
+      });
+  }
+  sendFileMessage(conversationOption, msgBody, hooks, options) {
+    const messageId = createMessageId();
+    const file = msgBody.file;
+    this._fileCache[messageId] = file;
+    msgBody = this._willSendMessage(
+      msgBody,
+      conversationOption.conversationType,
+      conversationOption.targetId,
+      MessageType.FILE
+    );
+    return this.decorateMessage(
+      conversationOption,
+      messageId,
+      new FileMessage({
+        name: file.name,
+        size: file.size,
+        type: file.type,
+        fileUrl: ""
+      }),
+      sendFileMessage(
+        conversationOption,
+        msgBody,
+        {
+          onProgress: progress => {
+            this.emit(CoreEvent.UPLOAD_PROGRESS, {
+              messageId,
+              progress
+            });
+            if (typeof hooks.onProgress === "function") {
+              hooks.onProgress(progress);
+            }
+          },
+          onComplete: fileInfo => {
+            this.emit(CoreEvent.UPLOAD_COMPLETE, {
+              messageId,
+              fileInfo
+            });
+            if (typeof hooks.onComplete === "function") {
+              hooks.onComplete(fileInfo);
+            }
+          }
+        },
+        options
+      )
+    );
+  }
+  resendMessage(conversation, message) {
+    // 删除之前失败消息
+    this.messageManager.deleteMessage(message);
+    this.emit(CoreEvent.DELETE_MESSAGE, [message]);
+    let newMessage = null;
+    const content = message.content;
+    switch (message.messageType) {
+      case "RC:TxtMsg":
+        let option = null;
+        if (content.mentionedInfo) {
+          option = content.mentionedInfo;
+          option.isMentioned = true;
+        }
+        newMessage = new TextMessage({ content: content.content });
+        this.sendMessage(conversation, newMessage, option);
+        break;
+      case "RC:ImgMsg":
+        const imgFile = this._fileCache[message.messageId];
+        this.sendImageMessage(conversation, imgFile, {
+          onProgress: () => {},
+          onComplete: () => {}
+        });
+        delete this._fileCache[message.messageId];
+        break;
+      case "RC:FileMsg":
+        const file = this._fileCache[message.messageId];
+        this.sendFileMessage(
+          conversation,
+          { file },
+          {
+            onProgress: () => {},
+            onComplete: () => {}
+          }
+        );
+        delete this._fileCache[message.messageId];
+        break;
+      case "RC:ReferenceMsg":
+        newMessage = new ReferenceMessage({
+          referMsgUserId: content.referMsgUserId,
+          referMsg: content.referMsg,
+          content: content.content,
+          objName: content.objName
+        });
+        this.sendMessage(conversation, newMessage, option);
+        break;
+    }
+  }
+  /**
+   * 消息发送前回调
+   * willSendMessage 初始化时有客户传递
+   * @param content
+   * @param conversationType
+   * @param targetId
+   * @param messageType
+   * @returns
+   */
+  _willSendMessage(content, conversationType, targetId, messageType) {
+    if (
+      this.customDisplayMessage &&
+      this.customDisplayMessage.willSendMessage &&
+      typeof this.customDisplayMessage.willSendMessage === "function"
+    ) {
+      content = this.customDisplayMessage.willSendMessage(
+        content,
+        conversationType,
+        targetId,
+        messageType
+      );
+    }
+    return content;
+  }
+  /**
+   * 发送消息的装饰器
+   * @param conversationOption
+   * @param messageId
+   * @param messageBody
+   * @param action
+   * @returns
+   */
+  decorateMessage(conversationOption, messageId, messageBody, action) {
+    // 构建显示用的临时消息
+    const tempMessage = {
+      targetId: conversationOption.targetId,
+      channelId: conversationOption.channelId,
+      conversationType: conversationOption.conversationType,
+      messageType: messageBody.messageType,
+      content: messageBody.content,
+      sentTime: Date.now(),
+      messageDirection: MessageDirection.SEND,
+      senderUserId: getCurrentUserId(),
+      receivedTime: undefined,
+      isPersited: messageBody.isPersited,
+      isCounted: messageBody.isCounted,
+      messageUId: "sending_" + messageId,
+      isOffLineMessage: false,
+      canIncludeExpansion: false,
+      receivedStatus: ReceivedStatus.UNREAD,
+      messageId
+    };
+    // 插入临时消息
+    this.messageManager.insertMessage(tempMessage);
+    // 发出增加消息的事件
+    this.emit(CoreEvent.MESSAGES, [tempMessage]);
+    // 更新会话的最后一条消息
+    const findResult = this.conversationManager.findConversation(
+      conversationOption
+    );
+    let resultConversation = null;
+    if (findResult) {
+      resultConversation = findResult.source[findResult.index];
+      const updatedConversation = {
+        conversation: resultConversation,
+        updatedItems: {
+          latestMessage: {
+            time: Date.now(),
+            val: tempMessage
+          }
+        }
+      };
+      this.conversationManager.update([updatedConversation]);
+      this.emit(CoreEvent.UPDATE_CONVERSATION, [
+        { ...resultConversation, latestMessage: tempMessage }
+      ]);
+    }
+    // 发出正在发送的事件通知
+    this.emit(CoreEvent.MESSAGE_SENDING, tempMessage);
+    // 发送消息
+    return action.then(res => {
+      const message = res.data ? res.data : tempMessage;
+      message.messageId = messageId;
+      if (message && message.content) {
+        // 发送成功调用 willDisplayMessages 处理消息
+        if (
+          this.customDisplayMessage &&
+          this.customDisplayMessage.willDisplayMessages &&
+          typeof this.customDisplayMessage.willDisplayMessages === "function"
+        ) {
+          message.content = this.customDisplayMessage.willDisplayMessages(
+            message.content,
+            message.conversationType,
+            message.targetId,
+            message.messageType
+          );
+        }
+      }
+      if (res.code === 0) {
+        // 发送成功后 imlib 会自动通知会话更新
+        // 只需要更新消息列表
+        this.messageManager.updateMessage(message);
+        // 消息发送成功的事件通知
+        this.emit(CoreEvent.MESSAGE_SEND_SUCCESS, message);
+        delete this._fileCache[message.messageId];
+      } else {
+        // 发送失败后,更新会话列表
+        if (findResult) {
+          // const resultConversation = findResult.source[findResult.index];
+          // 由于消息发送失败, messageUId为空,借用这个字段标志消息状态
+          message.messageUId = "fail_" + message.messageId;
+          const updatedConversation = {
+            conversation: resultConversation,
+            updatedItems: {
+              latestMessage: {
+                time: Date.now(),
+                val: message
+              }
+            }
+          };
+          this.conversationManager.update([updatedConversation]);
+          this.emit(CoreEvent.UPDATE_CONVERSATION, [
+            { ...resultConversation, latestMessage: message }
+          ]);
+        }
+        this.messageManager.updateMessage(message);
+        // 消息发送失败的事件通知
+        this.emit(CoreEvent.MESSAGE_SEND_FAIL, message);
+      }
+      return res;
+    });
+  }
+}
+
+export default new Core();

+ 28 - 0
src/layout/components/imkit/enum/event.js

@@ -0,0 +1,28 @@
+/**
+ * 事件类型
+ */
+/* eslint-disable */
+export var CoreEvent;
+(function (CoreEvent) {
+  CoreEvent["INIT"] = "INIT";
+  CoreEvent["CONNECTED"] = "CONNECTED";
+  CoreEvent["MESSAGES"] = "MESSAGES";
+  CoreEvent["CONVERSATION"] = "conversation";
+  CoreEvent["UPDATE_CONVERSATION"] = "udpate_conversation";
+  CoreEvent["TYPING_STATUS"] = "typing_status";
+  CoreEvent["PULL_OFFLINE_MESSAGE_FINISHED"] = "pull_offline_message_finished";
+  CoreEvent["READ_RECEIPT_RECEIVED"] = "read_receipt_received";
+  CoreEvent["MESSAGE_RECEIPT_REQUEST"] = "message_receipt_request";
+  CoreEvent["MESSAGE_RECEIPT_RESPONSE"] = "message_receipt_response";
+  CoreEvent["SWITCH_CONVERSATION"] = "switch_conversation";
+  CoreEvent["REFERENCE_MESSAGE"] = "reference_message";
+  CoreEvent["CLOSE_REFERENCE"] = "close_reference";
+  CoreEvent["UPDATE_MESSAGE"] = "update_message";
+  CoreEvent["DELETE_MESSAGE"] = "delete_message";
+  CoreEvent["MESSAGE_SENDING"] = "message_sending";
+  CoreEvent["MESSAGE_SEND_SUCCESS"] = "message_send_success";
+  CoreEvent["MESSAGE_SEND_FAIL"] = "message_send_fail";
+  CoreEvent["UPLOAD_PROGRESS"] = "upload_progress";
+  CoreEvent["UPLOAD_COMPLETE"] = "upload_complete";
+  CoreEvent["LANGUAGE_CHANGED"] = "language_changed";
+})(CoreEvent || (CoreEvent = {}));

+ 6 - 0
src/layout/components/imkit/enum/languages.js

@@ -0,0 +1,6 @@
+export var Languages;
+(function (Languages) {
+  Languages["ZH_CN"] = "zh_CN";
+  Languages["ZH_TW"] = "zh_TW";
+  Languages["EN"] = "en";
+})(Languages || (Languages = {}));

+ 20 - 0
src/layout/components/imkit/enum/logEnums.js

@@ -0,0 +1,20 @@
+export var LogTagId;
+(function (LogTagId) {
+  LogTagId["A_KIT_INIT_O"] = "A-kit-init-O";
+  LogTagId["A_KIT_MSG_INSERT_O"] = "A-kit-msg_insert-O";
+  LogTagId["A_KIT_MSG_DELETE_O"] = "A-kit-msg_delete-O";
+  LogTagId["A_KIT_MSG_UPDATE_O"] = "A-kit-msg_update-O";
+  LogTagId["A_KIT_WILL__MSGMSG_RECALL_O"] = "A-kit-msg_recall-O";
+  LogTagId["A_KIT_WILL_SEND_MSG_T"] = "A-kit-will_send_msg-T";
+  LogTagId["A_KIT_WILL_SEND_MSG_R"] = "A-kit-will_send_msg-R";
+  LogTagId["A_KIT_WILL_DISPLAY_MSG_T"] = "A-kit-will_display_msg-T";
+  LogTagId["A_KIT_WILL_DISPLAY_MSG_R"] = "A-kit-will_display_msg-R";
+  LogTagId["A_KIT_CONVERSATION_CREATE_O"] = "A-kit-conversation_create-O";
+  LogTagId["A_KIT_CONVERSATION_UPDATE_O"] = "A-kit-conversation_update-O";
+  LogTagId["A_KIT_CONVERSATION_DELETE_O"] = "A-kit-conversation_delete-O";
+  LogTagId["A_KIT_CONVERSATION_UNREAD_TIME_S"] = "A-kit-conversation_unread_time-S";
+  LogTagId["A_KIT_CONVERSATION_LISTENER_S"] = "A-kit-conversation_listener-S";
+  LogTagId["A_KIT_MESSAGE_LISTENER_S"] = "A-kit-message_listener-S";
+  LogTagId["A_KIT_READ_RECEIOT_LISTENER_S"] = "A-kit-read_receipt_listener-S";
+  LogTagId["A_KIT_FORWARD_O"] = "A-kit-forward-O";
+})(LogTagId || (LogTagId = {}));

+ 12 - 0
src/layout/components/imkit/images/close-btn.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>关  闭备份</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="10_单聊-引用消息样式" transform="translate(-1389.000000, -592.000000)" fill-rule="nonzero">
+            <g id="关--闭备份" transform="translate(1389.000000, 592.000000)">
+                <rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="32" height="32"></rect>
+                <path d="M17.7179871,16.2842712 L26.7775879,7.22467041 C27.2348938,6.7673645 27.2348938,6.01342773 26.7775879,5.55612183 L26.7281494,5.50668335 C26.2708435,5.04937744 25.5169067,5.04937744 25.0596008,5.50668335 L16,14.5786438 L6.94039917,5.50668335 C6.48309326,5.04937744 5.72915649,5.04937744 5.27185059,5.50668335 L5.22241211,5.55612183 C4.75274658,6.01342773 4.75274658,6.7673645 5.22241211,7.22467041 L14.2820129,16.2842712 L5.22241211,25.3438721 C4.7651062,25.801178 4.7651062,26.5551147 5.22241211,27.0124207 L5.27185059,27.0618591 C5.72915649,27.519165 6.48309326,27.519165 6.94039917,27.0618591 L16,18.0022583 L25.0596008,27.0618591 C25.5169067,27.519165 26.2708435,27.519165 26.7281494,27.0618591 L26.7775879,27.0124207 C27.2348938,26.5551147 27.2348938,25.801178 26.7775879,25.3438721 L17.7179871,16.2842712 Z" id="路径" fill="#A0A5AB"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 14 - 0
src/layout/components/imkit/images/download-icon.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="70px" height="70px" viewBox="0 0 70 70" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>下载icon</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="04_单聊-文件消息悬停点击状态" transform="translate(-965.000000, -528.000000)">
+            <g id="编组备份-5" transform="translate(564.000000, 501.000000)">
+                <g id="下载icon" transform="translate(402.000000, 28.000000)">
+                    <circle id="椭圆形" stroke="#E2E4E5" fill="#FFFFFF" cx="34" cy="34" r="34"></circle>
+                    <path d="M35.6211373,37.318474 L44.1096523,28.8835567 C44.5420955,28.453844 45.2432244,28.453844 45.6756676,28.8835567 C46.1081108,29.3132694 46.1081108,30.0099713 45.6756676,30.439684 L35.4075897,40.6429278 C34.9111495,41.1190241 34.124467,41.1190241 33.6280268,40.6429278 L23.3243576,30.4573673 C23.0446159,30.1793919 22.9353644,29.774234 23.0377569,29.3945125 C23.1401495,29.0147911 23.4386303,28.7181949 23.8207646,28.6164489 C24.2028989,28.5147028 24.6106313,28.6232646 24.890373,28.90124 L33.378888,37.3361573 L33.378888,20.2541235 C33.3242675,19.8258929 33.5235137,19.4048496 33.8902575,19.173507 C34.2570013,18.9421643 34.7252283,18.9421643 35.0919721,19.173507 C35.4587159,19.4048496 35.6579621,19.8258929 35.6033416,20.2541235 L35.6211373,37.318474 Z M21.9482731,47 C21.4063012,46.936163 21,46.5076987 21,46 C21,45.4923013 21.4063012,45.063837 21.9482731,45 L46.0517269,45 C46.5936988,45.063837 47,45.4923013 47,46 C47,46.5076987 46.5936988,46.936163 46.0517269,47 L21.9482731,47 Z" id="形状" fill="#0099FF" fill-rule="nonzero"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 13 - 0
src/layout/components/imkit/images/file-icon-hover.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="36px" height="36px" viewBox="0 0 36 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>文件icon</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="19_单聊-文件上传中" transform="translate(-553.000000, -710.000000)">
+            <g id="文件icon" transform="translate(553.000000, 710.000000)">
+                <rect id="矩形" x="0" y="0" width="36" height="36"></rect>
+                <path d="M31.5,5 C32.1905692,5 32.8156168,5.27963505 33.2679909,5.73200912 C33.7203649,6.1843832 34,6.8094308 34,7.5 L34,7.5 L34,28.5 C34,29.1905692 33.7203649,29.8156168 33.2679909,30.2679909 C32.8156168,30.7203649 32.1905692,31 31.5,31 L31.5,31 L5.5,31 C4.8094308,31 4.1843832,30.7203649 3.73200912,30.2679909 C3.27963505,29.8156168 3,29.1905692 3,28.5 L3,28.5 L3,11 C3,10.3094308 3.27963505,9.6843832 3.73200912,9.23200912 C4.1843832,8.77963505 4.8094308,8.5 5.5,8.5 L5.5,8.5 L17.9142136,8.5 L21.195388,5.21882553 C21.3361409,5.07807268 21.5273437,5 21.7265625,5 L21.7265625,5 Z" id="路径" stroke="#0099FF" stroke-width="2" fill-rule="nonzero" transform="translate(18.500000, 18.000000) scale(-1, 1) translate(-18.500000, -18.000000) "></path>
+                <path d="M27.7222222,27 L9.27777778,27 C8.57100694,27 8,26.553125 8,26 C8,25.446875 8.57100694,25 9.27777778,25 L27.7222222,25 C28.4289931,25 29,25.446875 29,26 C29,26.553125 28.4289931,27 27.7222222,27 Z" id="路径" fill="#0099FF" fill-rule="nonzero" transform="translate(18.500000, 26.000000) scale(-1, 1) translate(-18.500000, -26.000000) "></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 13 - 0
src/layout/components/imkit/images/file-icon.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="36px" height="36px" viewBox="0 0 36 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>文件icon</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="03_向下滚动时loading" transform="translate(-553.000000, -710.000000)">
+            <g id="文件icon" transform="translate(553.000000, 710.000000)">
+                <rect id="矩形" x="0" y="0" width="36" height="36"></rect>
+                <path d="M31.5,5 C32.1905692,5 32.8156168,5.27963505 33.2679909,5.73200912 C33.7203649,6.1843832 34,6.8094308 34,7.5 L34,7.5 L34,28.5 C34,29.1905692 33.7203649,29.8156168 33.2679909,30.2679909 C32.8156168,30.7203649 32.1905692,31 31.5,31 L31.5,31 L5.5,31 C4.8094308,31 4.1843832,30.7203649 3.73200912,30.2679909 C3.27963505,29.8156168 3,29.1905692 3,28.5 L3,28.5 L3,11 C3,10.3094308 3.27963505,9.6843832 3.73200912,9.23200912 C4.1843832,8.77963505 4.8094308,8.5 5.5,8.5 L5.5,8.5 L17.9142136,8.5 L21.195388,5.21882553 C21.3361409,5.07807268 21.5273437,5 21.7265625,5 L21.7265625,5 Z" id="路径" stroke="#777777" stroke-width="2" fill-rule="nonzero" transform="translate(18.500000, 18.000000) scale(-1, 1) translate(-18.500000, -18.000000) "></path>
+                <path d="M27.7222222,27 L9.27777778,27 C8.57100694,27 8,26.553125 8,26 C8,25.446875 8.57100694,25 9.27777778,25 L27.7222222,25 C28.4289931,25 29,25.446875 29,26 C29,26.553125 28.4289931,27 27.7222222,27 Z" id="路径" fill="#777777" fill-rule="nonzero" transform="translate(18.500000, 26.000000) scale(-1, 1) translate(-18.500000, -26.000000) "></path>
+            </g>
+        </g>
+    </g>
+</svg>

BIN
src/layout/components/imkit/images/ic_group_list_mussic_team_stu.png


BIN
src/layout/components/imkit/images/ic_group_list_vip.png


BIN
src/layout/components/imkit/images/icon_repertoire_play.png


BIN
src/layout/components/imkit/images/icon_training.png


+ 14 - 0
src/layout/components/imkit/images/image-icon-hover.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="36px" height="36px" viewBox="0 0 36 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>图片icon</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="17_单聊-发送图片消息" transform="translate(-487.000000, -860.000000)">
+            <g id="图片icon" transform="translate(487.000000, 860.000000)">
+                <rect id="矩形" x="0" y="0" width="36" height="36"></rect>
+                <path d="M30.5901639,5 C31.2544776,5 31.855807,5.27216318 32.2913229,5.71076061 C32.7293826,6.15191973 33,6.76137857 33,7.43396226 L33,7.43396226 L33,28.5660377 C33,29.2386214 32.7293826,29.8480803 32.2913229,30.2892394 C31.855807,30.7278368 31.2544776,31 30.5901639,31 L30.5901639,31 L5.40983607,31 C4.771478,31 4.15958495,30.744071 3.70831297,30.2896061 C3.25440583,29.8324873 3,29.2123435 3,28.5660377 L3,28.5660377 L3,7.43396226 C3,6.76137857 3.27061743,6.15191973 3.70867708,5.71076061 C4.14419303,5.27216318 4.7455224,5 5.40983607,5 L5.40983607,5 Z" id="路径" stroke="#0099FF" stroke-width="2" fill-rule="nonzero"></path>
+                <path d="M24.3235462,16.7538759 C25.1224589,16.6964511 25.9370374,16.845791 26.6799601,17.2089493 L26.6799601,17.2089493 L30.5168124,19.0831439 C31.2787848,19.4555752 31.9011108,20.0217743 32.3323271,20.7038505 C32.7604922,21.3811007 33,22.1725396 33,22.9996626 L33,22.9996626 L33,28.6676805 C33,29.3088456 32.7318775,29.8884438 32.3021262,30.3085164 C31.8640336,30.7367425 31.2584624,30.9999996 30.5909025,30.9999996 L30.5909025,30.9999996 L5.9797822,30.9999998 C5.72555446,31.0001481 5.48563801,30.9204214 5.29037194,30.7805562 C5.10133094,30.6451499 4.95411217,30.4530261 4.8799608,30.223132 C4.80734227,29.9979902 4.81368266,29.7628409 4.88761991,29.5501594 C4.96321336,29.3327138 5.1090076,29.1392248 5.31145973,29.0008441 L5.31145973,29.0008441 L22.0631083,17.5398086 C22.7427657,17.0749646 23.5254857,16.8112395 24.3235462,16.7538759 Z" id="路径" stroke="#0099FF" stroke-width="2" fill-rule="nonzero"></path>
+                <path d="M10.6857143,14.8387097 C11.9480794,14.8387097 12.9714286,13.8277398 12.9714286,12.5806452 C12.9714286,11.3335506 11.9480794,10.3225806 10.6857143,10.3225806 C9.42334912,10.3225806 8.4,11.3335506 8.4,12.5806452 C8.4,13.8277397 9.42334912,14.8387097 10.6857143,14.8387097 L10.6857143,14.8387097 Z" id="路径" fill="#0099FF" fill-rule="nonzero"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 14 - 0
src/layout/components/imkit/images/image-icon.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="36px" height="36px" viewBox="0 0 36 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>图片icon</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="03_向下滚动时loading" transform="translate(-487.000000, -710.000000)">
+            <g id="图片icon" transform="translate(487.000000, 710.000000)">
+                <rect id="矩形" x="0" y="0" width="36" height="36"></rect>
+                <path d="M30.5901639,5 C31.2544776,5 31.855807,5.27216318 32.2913229,5.71076061 C32.7293826,6.15191973 33,6.76137857 33,7.43396226 L33,7.43396226 L33,28.5660377 C33,29.2386214 32.7293826,29.8480803 32.2913229,30.2892394 C31.855807,30.7278368 31.2544776,31 30.5901639,31 L30.5901639,31 L5.40983607,31 C4.771478,31 4.15958495,30.744071 3.70831297,30.2896061 C3.25440583,29.8324873 3,29.2123435 3,28.5660377 L3,28.5660377 L3,7.43396226 C3,6.76137857 3.27061743,6.15191973 3.70867708,5.71076061 C4.14419303,5.27216318 4.7455224,5 5.40983607,5 L5.40983607,5 Z" id="路径" stroke="#777777" stroke-width="2" fill-rule="nonzero"></path>
+                <path d="M24.3235462,16.7538759 C25.1224589,16.6964511 25.9370374,16.845791 26.6799601,17.2089493 L26.6799601,17.2089493 L30.5168124,19.0831439 C31.2787848,19.4555752 31.9011108,20.0217743 32.3323271,20.7038505 C32.7604922,21.3811007 33,22.1725396 33,22.9996626 L33,22.9996626 L33,28.6676805 C33,29.3088456 32.7318775,29.8884438 32.3021262,30.3085164 C31.8640336,30.7367425 31.2584624,30.9999996 30.5909025,30.9999996 L30.5909025,30.9999996 L5.9797822,30.9999998 C5.72555446,31.0001481 5.48563801,30.9204214 5.29037194,30.7805562 C5.10133094,30.6451499 4.95411217,30.4530261 4.8799608,30.223132 C4.80734227,29.9979902 4.81368266,29.7628409 4.88761991,29.5501594 C4.96321336,29.3327138 5.1090076,29.1392248 5.31145973,29.0008441 L5.31145973,29.0008441 L22.0631083,17.5398086 C22.7427657,17.0749646 23.5254857,16.8112395 24.3235462,16.7538759 Z" id="路径" stroke="#777777" stroke-width="2" fill-rule="nonzero"></path>
+                <path d="M10.6857143,14.8387097 C11.9480794,14.8387097 12.9714286,13.8277398 12.9714286,12.5806452 C12.9714286,11.3335506 11.9480794,10.3225806 10.6857143,10.3225806 C9.42334912,10.3225806 8.4,11.3335506 8.4,12.5806452 C8.4,13.8277397 9.42334912,14.8387097 10.6857143,14.8387097 L10.6857143,14.8387097 Z" id="路径" fill="#777777" fill-rule="nonzero"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 28 - 0
src/layout/components/imkit/images/loading.svg

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="28px" height="28px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>loading</title>
+    <defs>
+        <linearGradient x1="94.0869141%" y1="0%" x2="94.0869141%" y2="90.559082%" id="linearGradient-1">
+            <stop stop-color="#8F949A" stop-opacity="0" offset="0%"></stop>
+            <stop stop-color="#8F949A" stop-opacity="0.3" offset="100%"></stop>
+        </linearGradient>
+        <linearGradient x1="100%" y1="8.67370605%" x2="100%" y2="90.6286621%" id="linearGradient-2">
+            <stop stop-color="#8F949A" offset="0%"></stop>
+            <stop stop-color="#8F949A" stop-opacity="0.3" offset="100%"></stop>
+        </linearGradient>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="03_向下滚动时loading" transform="translate(-142.000000, -911.000000)" fill-rule="nonzero">
+            <g id="网络不可用提示" transform="translate(0.000000, 579.000000)">
+                <g id="loading" transform="translate(142.000000, 332.000000)">
+                    <rect id="矩形" x="0" y="0" width="28" height="28"></rect>
+                    <g id="LoadingBrand" transform="translate(2.000000, 2.000000)">
+                        <path d="M12,0 C18.627417,0 24,5.372583 24,12 C24,18.627417 18.627417,24 12,24 L12,21.9 C17.467619,21.9 21.9,17.467619 21.9,12 C21.9,6.53238099 17.467619,2.1 12,2.1 L12,0 Z" id="路径" fill="url(#linearGradient-1)"></path>
+                        <path d="M12,0 L12,2.1 C6.53238099,2.1 2.1,6.53238099 2.1,12 C2.1,17.467619 6.53238099,21.9 12,21.9 L12,24 C5.372583,24 0,18.627417 0,12 C0,5.372583 5.372583,0 12,0 Z" id="路径" fill="url(#linearGradient-2)"></path>
+                        <circle id="Oval" fill="#8F949A" cx="12.15" cy="1.05" r="1.05"></circle>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

File diff suppressed because it is too large
+ 7 - 0
src/layout/components/imkit/images/message-icon-apk.svg


+ 12 - 0
src/layout/components/imkit/images/message-icon-audio.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="84px" height="84px" viewBox="0 0 84 84" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon_文件类型_mp3</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="状态切图" transform="translate(-211.000000, -382.000000)">
+            <g id="icon_文件类型_mp3" transform="translate(211.000000, 382.000000)">
+                <rect id="Rectangle-38-Copy" fill="#89D042" x="0" y="0" width="84" height="84" rx="6"></rect>
+                <path d="M37.4752227,31.7565323 L51.8608171,56.244234 C54.459752,60.668239 52.4612815,66.5840158 47.3971084,69.4574913 C42.3329353,72.3309669 36.1207589,71.0740144 33.5218241,66.6500094 C30.9228892,62.2260044 32.9213597,56.3102276 37.9855328,53.4367521 C40.8660447,51.8023134 44.1179766,51.5042158 46.8651274,52.3651714 L31,25.3589528 L31.2484838,25.21796 L31.2403144,25.2062983 C31.2403144,25.2062983 31.4234215,25.1108727 31.7475961,24.9347574 L33.0376659,24.2027555 L33.0432403,24.2122444 C36.530448,22.2215296 43.9505491,17.6293507 45.0086306,14.0442685 C46.3991204,9.33289189 52.1349271,21.8132104 46.3765051,26.6334024 C42.5442047,29.8412993 39.2501194,31.2038951 37.4752227,31.7565323 L37.4752227,31.7565323 Z" id="Combined-Shape" fill="#FFFFFF"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 12 - 0
src/layout/components/imkit/images/message-icon-image.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="168px" height="168px" viewBox="0 0 168 168" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon_文件类型_图片 copy 2</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="状态切图" transform="translate(-696.000000, -2146.000000)">
+            <g id="icon_文件类型_图片-copy-2" transform="translate(696.000000, 2146.000000)">
+                <rect id="Rectangle-38-Copy-4" fill="#89D042" x="0" y="0" width="168" height="168" rx="10.02"></rect>
+                <path d="M128.563996,49.19137 C128.907692,49.19137 129.218851,49.3306804 129.444085,49.555915 C129.637143,49.7489731 129.767073,50.0051592 129.800256,50.2908537 L129.80863,50.4360044 L129.808,105.93 L115.409741,84.6701146 L115.24236,84.4366801 C114.60881,83.6013686 113.760921,82.9939716 112.819161,82.652492 C111.823141,82.291338 110.71292,82.1683448 109.218162,82.6422959 L109.218162,82.6422959 L98.3209944,86.754653 L66.5095418,64.776697 L66.2726279,64.6219136 C65.0750174,63.8832148 63.6874464,63.6723727 62.3646016,63.9706437 C61.0725134,64.2619797 59.8131007,65.0478471 58.9275533,66.4103817 L58.9275533,66.4103817 L36.191,105.875 L36.19137,50.4360044 C36.19137,50.0923081 36.3306804,49.7811495 36.555915,49.555915 C36.7489731,49.3628568 37.0051592,49.2329265 37.2908537,49.1997436 L37.4360044,49.19137 L128.563996,49.19137 Z M105.343134,57.1580973 C103.23716,57.1580973 101.063939,58.1523649 99.4782904,59.9064072 C98.0797406,61.4534811 97.1625819,63.5573132 97.1625819,65.8684932 C97.1625819,68.1796731 98.0797406,70.2835052 99.4782904,71.8305791 C101.063939,73.5846214 103.23716,74.578889 105.343134,74.578889 C108.053099,74.578889 110.300142,73.6419754 111.908314,72.2188087 C113.725598,70.6105877 114.770981,68.3593053 114.770981,65.8684932 C114.770981,63.377681 113.725598,61.1263986 111.908314,59.5181776 C110.300142,58.0950109 108.053099,57.1580973 105.343134,57.1580973 Z" id="Combined-Shape" stroke="#FFFFFF" stroke-width="6.38274" fill="#FFFFFF"></path>
+            </g>
+        </g>
+    </g>
+</svg>

File diff suppressed because it is too large
+ 8 - 0
src/layout/components/imkit/images/message-icon-pdf.svg


+ 19 - 0
src/layout/components/imkit/images/message-icon-plain.svg

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="84px" height="84px" viewBox="0 0 84 84" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon_文件类型_txt</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="状态切图" transform="translate(-211.000000, -1124.000000)">
+            <g id="icon_文件类型_txt" transform="translate(211.000000, 1124.000000)">
+                <g id="Group-12-Copy" fill="#3EA9FF">
+                    <rect id="Rectangle-38-Copy-4" x="0" y="0" width="84" height="84" rx="6"></rect>
+                </g>
+                <g id="Group-4" transform="translate(21.000000, 23.000000)" fill="#FFFFFF">
+                    <g id="Group-3" transform="translate(0.279500, 0.374706)">
+                        <rect id="Rectangle-5" x="0" y="0" width="41.925" height="7.02573529" rx="0.637"></rect>
+                        <path d="M1.69325,19.9253676 L40.23175,19.9253676 C40.5835554,19.9253676 40.86875,20.2105623 40.86875,20.5623676 L40.86875,26.2758676 C40.86875,26.627673 40.5835554,26.9128676 40.23175,26.9128676 L1.69325,26.9128676 C1.34144461,26.9128676 1.05625,26.627673 1.05625,26.2758676 L1.05625,20.5623676 C1.05625,20.2105623 1.34144461,19.9253676 1.69325,19.9253676 Z" id="Rectangle-5-Copy" transform="translate(20.962500, 23.419118) rotate(-270.000000) translate(-20.962500, -23.419118) "></path>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 12 - 0
src/layout/components/imkit/images/message-icon-ppt.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="84px" height="84px" viewBox="0 0 84 84" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon_文件类型_ppt</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="状态切图" transform="translate(-211.000000, -912.000000)">
+            <g id="icon_文件类型_ppt" transform="translate(211.000000, 912.000000)">
+                <rect id="Rectangle-38-Copy-5" fill="#FF8654" x="0" y="0" width="84" height="84" rx="6"></rect>
+                <path d="M26,20 L45.8607182,20 C48.4722655,20 50.7769215,20.4202914 52.7747552,21.2608868 C54.7725889,22.1014822 56.4570116,23.2007058 57.828074,24.5585907 C59.1991363,25.9164755 60.2372108,27.4424566 60.9423286,29.1365796 C61.6474464,30.8307026 62,32.5183342 62,34.1995249 C62,36.0359026 61.6409176,37.8075924 60.9227421,39.5146477 C60.2045666,41.2217029 59.1599633,42.7412179 57.788901,44.0732383 C56.4178386,45.4052587 54.7334159,46.4721522 52.7355822,47.2739509 C50.7377484,48.0757496 48.44615,48.4766429 45.8607182,48.4766429 L34.265506,48.4766429 L34.265506,69 L26,69 L26,20 Z M34.265506,42.8511481 L45.8607182,42.8511481 C46.9053371,42.8511481 47.9107677,42.6248373 48.8770403,42.172209 C49.8433128,41.7195807 50.6855242,41.0988427 51.4036997,40.3099762 C52.1218752,39.5211098 52.696407,38.5964687 53.1273123,37.5360253 C53.5582176,36.4755819 53.773667,35.3375622 53.773667,34.1219319 C53.773667,32.9321661 53.5712751,31.8264765 53.1664853,30.8048298 C52.7616955,29.7831831 52.20675,28.9038042 51.5016322,28.1666667 C50.7965144,27.4295292 49.9608318,26.8475872 48.9945593,26.4208234 C48.0282868,25.9940596 46.9836835,25.7806809 45.8607182,25.7806809 L34.265506,25.7806809 L34.265506,42.8511481 Z" id="P" fill="#FFFFFF"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 17 - 0
src/layout/components/imkit/images/message-icon-text.svg

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="168px" height="168px" viewBox="0 0 168 168" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon_文件类型_文件</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="状态切图" transform="translate(-696.000000, -3186.000000)" fill-rule="nonzero">
+            <g id="icon_文件类型_文件" transform="translate(696.000000, 3186.000000)">
+                <g id="apk" fill="#A0C538">
+                    <path d="M15.8941947,0.163857698 L152.223783,0.163857698 C160.875468,0.163857698 167.95412,7.24250938 167.95412,15.8941947 L167.95412,152.223783 C167.95412,160.875468 160.875468,167.95412 152.223783,167.95412 L15.8941947,167.95412 C7.24250938,167.95412 0.163857698,160.875468 0.163857698,152.223783 L0.163857698,15.8941947 C0.163857698,7.24250938 7.24250938,0.163857698 15.8941947,0.163857698 Z" id="路径"></path>
+                </g>
+                <g id="文件" transform="translate(45.000000, 36.000000)" fill="#FFFFFF">
+                    <path d="M70.9217466,0 L6.36832192,0 C2.57106164,0 0.0395547945,2.53043478 0.0395547945,6.32608696 L0.0395547945,89.8304348 C0.0395547945,92.9934783 3.20393836,96.1565217 6.36832192,96.1565217 L46.8724315,96.1565217 C50.6696918,96.1565217 53.2011986,93.626087 53.2011986,89.8304348 L53.2011986,76.5456522 C53.2011986,73.3826087 56.3655822,70.2195652 59.5299658,70.2195652 L70.2888699,70.2195652 C74.0861301,70.2195652 76.617637,67.6891304 76.617637,63.8934783 L76.617637,5.69347826 C77.8833904,2.53043478 74.7190068,0 70.9217466,0 Z M50.0368151,51.873913 C50.0368151,53.7717391 48.7710616,55.0369565 46.8724315,55.0369565 L18.3929795,55.0369565 C16.4943493,55.0369565 15.2285959,53.7717391 15.2285959,51.873913 L15.2285959,51.2413043 C15.2285959,49.3434783 16.4943493,48.0782609 18.3929795,48.0782609 L46.8724315,48.0782609 C48.7710616,48.0782609 50.0368151,49.3434783 50.0368151,51.873913 Z M63.9601027,34.7934783 C63.9601027,36.6913043 62.6943493,37.9565217 60.7957192,37.9565217 L18.3929795,37.9565217 C16.4943493,37.323913 14.5957192,36.0586957 14.5957192,34.7934783 L14.5957192,34.1608696 C14.5957192,32.2630435 15.8614726,30.9978261 17.7601027,30.9978261 L60.1628425,30.9978261 C62.6943493,30.9978261 63.9601027,32.2630435 63.9601027,34.7934783 L63.9601027,34.7934783 Z M63.9601027,17.7130435 C63.9601027,19.6108696 62.6943493,20.876087 60.7957192,20.876087 L18.3929795,20.876087 C16.4943493,20.876087 15.2285959,19.6108696 15.2285959,17.7130435 L15.2285959,17.0804348 C15.2285959,15.1826087 16.4943493,13.9173913 18.3929795,13.9173913 L60.7957192,13.9173913 C62.6943493,13.9173913 63.9601027,15.1826087 63.9601027,17.7130435 Z" id="形状"></path>
+                    <path d="M57.6313356,80.973913 L57.6313356,89.8304348 C57.6313356,92.9934783 61.4285959,94.2586957 63.327226,92.3608696 L75.9847603,79.7086957 C77.8833904,77.8108696 76.617637,74.0152174 73.4532534,74.0152174 L64.5929795,74.0152174 C60.7957192,74.0152174 57.6313356,77.1782609 57.6313356,80.973913 Z" id="路径"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 15 - 0
src/layout/components/imkit/images/message-icon-unknown.svg

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="84px" height="84px" viewBox="0 0 84 84" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon_文件类型_未知</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0.7">
+        <g id="状态切图" transform="translate(-211.000000, -700.000000)">
+            <g id="icon_文件类型_未知" transform="translate(211.000000, 700.000000)">
+                <rect id="Rectangle-38-Copy-4" fill="#B5B9BC" x="0" y="0" width="84" height="84" rx="6"></rect>
+                <g id="Group-8" transform="translate(21.000000, 14.000000)">
+                    <path d="M25.5463542,0 L41.803,16.17 L41.803125,53.6205853 C41.803125,54.789725 40.8514652,55.7375 39.6726308,55.7375 L2.13049424,55.7375 C0.953854761,55.7375 0,54.7934579 0,53.6205853 L0,2.11691469 C0,0.94777499 0.951659796,0 2.13049424,0 L25.5463542,0 Z" id="Combined-Shape" fill="#FFFFFF"></path>
+                    <path d="M25.5463542,0 L41.8419639,16.2088601 L26.6155546,16.2088601 C26.0250515,16.2088601 25.5463542,15.7412409 25.5463542,15.1453515 L25.5463542,0 Z" id="Triangle-3" fill="#D6D6D6"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 12 - 0
src/layout/components/imkit/images/message-icon-video.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="84px" height="84px" viewBox="0 0 84 84" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon_文件类型_mp4</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="状态切图" transform="translate(-211.000000, -1018.000000)">
+            <g id="icon_文件类型_mp4" transform="translate(211.000000, 1018.000000)">
+                <rect id="Rectangle-38-Copy-6" fill="#A396E8" x="0" y="0" width="84" height="84" rx="6"></rect>
+                <path d="M62.3448429,25 C63.8112457,25 65,26.1845679 65,27.6594157 L65,57.3405843 C65,58.809339 63.8166887,60 62.3448429,60 L21.6551571,60 C20.1887543,60 19,58.8154321 19,57.3405843 L19,27.6594157 C19,26.190661 20.1833113,25 21.6551571,25 L62.3448429,25 Z M37.1,35.2659696 L37.1,50.8906993 C37.1,52.3543512 38.0536133,52.829968 39.2315359,51.9518406 L49.0029469,44.6673692 C50.1801617,43.7897695 50.1808695,42.367427 49.0029469,41.4892997 L39.2315359,34.2048282 C38.0543211,33.3272286 37.1,33.8007712 37.1,35.2659696 Z" id="Combined-Shape" fill="#FFFFFF"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 14 - 0
src/layout/components/imkit/images/message-icon-word.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="84px" height="84px" viewBox="0 0 84 84" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon_文件类型_doc</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="状态切图" transform="translate(-211.000000, -274.000000)">
+            <g id="icon_文件类型_doc" transform="translate(211.000000, 274.000000)">
+                <g id="Group-6" fill="#60A7FE">
+                    <rect id="Rectangle-38" x="0" y="0" width="84" height="84" rx="6"></rect>
+                </g>
+                <path d="M22.9545455,20 L22.954,54.443 L41.4443464,36.0050962 L46.2198429,40.7672506 L46.213,40.773 L60.045,54.438 L60.0454545,20 L67,20 L67,63.9223982 L60.153,63.922 L60.0792699,64 L41.5,45.473 L22.9594269,63.9626805 L22.921,63.922 L16,63.9223982 L16,20 L22.9545455,20 Z" id="Combined-Shape" fill="#FFFFFF"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 12 - 0
src/layout/components/imkit/images/message-icon-xls.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="84px" height="84px" viewBox="0 0 84 84" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon_文件类型_excel</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="状态切图" transform="translate(-211.000000, -488.000000)">
+            <g id="icon_文件类型_excel" transform="translate(211.000000, 488.000000)">
+                <rect id="Rectangle-38-Copy-2" fill="#02D1A5" x="0" y="0" width="84" height="84" rx="6"></rect>
+                <path d="M63,21 L46.971,41.5 L63,62 L53.0385819,61.9767905 L41.999,47.858 L30.9614181,61.9767905 L21,62 L37.028,41.5 L21,21 L30.9614181,21.0232095 L42,35.141 L53.0385819,21.0232095 L63,21 Z" id="Combined-Shape" fill="#FFFFFF"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 22 - 0
src/layout/components/imkit/images/message-icon-zip.svg

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="84px" height="84px" viewBox="0 0 84 84" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon_文件类型_zip</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="状态切图" transform="translate(-211.000000, -806.000000)">
+            <g id="icon_文件类型_zip" transform="translate(211.000000, 806.000000)">
+                <g id="Group-12-Copy" fill="#FFBD4C">
+                    <rect id="Rectangle-38-Copy-4" x="0" y="0" width="84" height="84" rx="6"></rect>
+                </g>
+                <g id="Group-7" transform="translate(33.000000, 0.000000)" fill="#FFFFFF">
+                    <rect id="Rectangle-3" x="8.5995" y="0" width="8.5995" height="20.9015625"></rect>
+                    <rect id="Rectangle-3-Copy" x="0" y="20.9015625" width="8.5995" height="8.360625"></rect>
+                    <rect id="Rectangle-3-Copy-2" x="8.5995" y="29.2621875" width="8.5995" height="8.360625"></rect>
+                    <rect id="Rectangle-3-Copy-3" x="0" y="37.6228125" width="8.5995" height="8.360625"></rect>
+                    <rect id="Rectangle-3-Copy-4" x="8.5995" y="45.9834375" width="8.5995" height="8.360625"></rect>
+                    <rect id="Rectangle-3-Copy-5" x="8.5995" y="54.3440625" width="8.5995" height="8.360625"></rect>
+                    <rect id="Rectangle-3-Copy-6" x="0" y="54.3440625" width="8.5995" height="8.360625"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

File diff suppressed because it is too large
+ 15 - 0
src/layout/components/imkit/images/no-conversation.svg


+ 12 - 0
src/layout/components/imkit/images/notification-disable.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="27px" height="26px" viewBox="0 0 27 26" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>免打扰</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="01_会话列表概览" transform="translate(-401.000000, -638.000000)" fill-rule="nonzero">
+            <g id="免打扰" transform="translate(401.400000, 638.000000)">
+                <rect id="矩形" x="0" y="0" width="26" height="26"></rect>
+                <path d="M7.3125,12.4685 L17.6195,18.6875 L18.6875,18.6875 C19.5849627,18.6875 20.3125,19.4150373 20.3125,20.3125 C20.3125,21.2099627 19.5849627,21.9375 18.6875,21.9375 L6.5,21.9375 C5.60253728,21.9375 4.875,21.2099627 4.875,20.3125 C4.875,19.4150373 5.60253728,18.6875 6.5,18.6875 L7.3125,18.6875 L7.3125,12.4685 Z M12.59375,4.0625 C13.266847,4.0625 13.8125,4.60815296 13.8125,5.28125 L13.8129814,5.82893336 C16.1419326,6.37930353 17.875,8.47163697 17.875,10.96875 L17.8739554,14.9605 L22.0559554,17.4225221 L21.2211999,18.8968273 L4.18095543,8.86493951 L5.01571092,7.39063436 L7.70280176,8.97252028 C8.34075189,7.41109522 9.70468607,6.22334793 11.3750826,5.82880011 L11.375,5.28125 C11.375,4.60815296 11.920653,4.0625 12.59375,4.0625 Z" id="形状结合" fill="#D1D1D1"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 16 - 0
src/layout/components/imkit/images/send-fail.svg

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="25px" height="24px" viewBox="0 0 25 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>感叹号</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="02_单聊-单聊向上滚动时-loading" transform="translate(-104.000000, -639.000000)" fill-rule="nonzero">
+            <g id="感叹号" transform="translate(104.400000, 639.000000)">
+                <rect id="矩形" x="0" y="0" width="24" height="24"></rect>
+                <g id="编组-24" transform="translate(1.500000, 1.500000)">
+                    <circle id="椭圆形" fill="#FF4141" cx="10.5" cy="10.5" r="10.5"></circle>
+                    <rect id="矩形" fill="#FFFFFF" x="9.75" y="5.25" width="1.5" height="7.5"></rect>
+                    <circle id="椭圆形" fill="#FFFFFF" cx="10.5" cy="15" r="1"></circle>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 16 - 0
src/layout/components/imkit/images/sending.svg

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="27px" height="26px" viewBox="0 0 27 26" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>正在发送</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="02_单聊-单聊向上滚动时-loading" transform="translate(-104.000000, -534.000000)" fill-rule="nonzero">
+            <g id="编组备份-4" transform="translate(0.400000, 475.000000)">
+                <g id="正在发送" transform="translate(104.000000, 59.000000)">
+                    <rect id="矩形" x="0" y="0" width="26" height="26"></rect>
+                    <g id="编组-25" transform="translate(2.000000, 4.000000)" fill="#D8D8D8">
+                        <path d="M9.85714286,0 L9.85714286,4.90909091 L15.6071429,4.90909091 L15.6071429,13.9090909 L9.85714286,13.9090909 L9.85714286,18 L0,9.02759355 L9.85714286,0 Z M19.7142857,4.90909091 L19.7142857,13.9090909 L17.25,13.9090909 L17.25,4.90909091 L19.7142857,4.90909091 Z M23,4.90909091 L23,13.9090909 L21.3571429,13.9090909 L21.3571429,4.90909091 L23,4.90909091 Z" id="形状结合"></path>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 14 - 0
src/layout/components/imkit/images/sight-playing.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>编组 10</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="16_单聊-小视频消息-2" transform="translate(-1136.000000, -503.000000)" fill-rule="nonzero">
+            <g id="编组-11" transform="translate(1020.000000, 451.000000)">
+                <g id="编组-10" transform="translate(116.000000, 52.000000)">
+                    <circle id="椭圆形" stroke="#FFFFFF" stroke-width="2" cx="36" cy="36" r="35"></circle>
+                    <path d="M31.5364435,23.9766319 L50.249592,35.8715424 C50.7156851,36.1678119 50.8533543,36.785829 50.5570848,37.2519221 C50.4756122,37.3800953 50.3660151,37.4880272 50.2366098,37.5675281 L31.5234613,49.0640532 C31.0528873,49.3531529 30.4370502,49.206039 30.1479505,48.735465 C30.0512117,48.5780011 30,48.3968098 30,48.2120038 L30,24.8205681 C30,24.2682834 30.4477153,23.8205681 31,23.8205681 C31.1900099,23.8205681 31.3760872,23.8747023 31.5364435,23.9766319 Z" id="矩形" fill="#FFFFFF"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 6 - 0
src/layout/components/imkit/index.js

@@ -0,0 +1,6 @@
+import core from "./core";
+export * from "./enum/event";
+export * from "./service";
+// export * from "./interface";
+// export * from "./enum/languages";
+export { core, core as imkit };

+ 382 - 0
src/layout/components/imkit/message.js

@@ -0,0 +1,382 @@
+import {
+  getHistoryMessages,
+  MessageDirection,
+  ReceivedStatus,
+  ConversationType,
+} from "@rongcloud/imlib-next";
+import { logger } from "@rongcloud/engine";
+import { LogTagId } from "./enum/logEnums";
+import { getConversationKey, isEqual } from "./utils";
+export default class MessageManager {
+  constructor() {
+    /**
+     * 最近的消息列表
+     */
+    this.localMessages = {};
+  }
+  /**
+   * 设置当前会话
+   */
+  set currentConversation(conversation) {
+    this._currentConversation = conversation;
+  }
+  /**
+   * 获取当前会话
+   */
+  get currentConversation() {
+    return this._currentConversation;
+  }
+  /**
+   * 获取消息,如果本地消息不足,则从服务端拉取
+   * @param conversation 会话
+   * @param start 开始时间,默认从最新一条的时间开始
+   * @param count 获取数量,默认20条
+   * @returns
+   */
+  getMessages(conversation, startTime = 0, count = 20, unReadTime) {
+    count = Math.min(20, Math.max(1, count));
+    this._unReadTime = unReadTime;
+    const result = this.getLocalMessage(conversation, startTime, count);
+    // 拿到本地消息匹配消息是否已读
+    result.list &&
+      result.list.forEach((message) => {
+        const isPrivat = message.conversationType === ConversationType.PRIVATE;
+        const isSendUser = message.messageDirection === MessageDirection.SEND;
+        const isUnRead = message.sentTime <= this._unReadTime;
+        if (isPrivat && isSendUser && isUnRead) {
+          // 单聊,自己发送的消息
+          message.receivedStatus = ReceivedStatus.READ;
+        }
+      });
+    if (result.list.length >= count) {
+      // 消息数量能满足count
+      return Promise.resolve(result);
+    } else {
+      if (result.hasMore === false) {
+        // 服务器没有更多历史消息
+        return Promise.resolve(result);
+      } else {
+        const c = getConversationKey(conversation);
+        let timestamp = 0;
+        if (this.localMessages[c] && this.localMessages[c].list.length > 0) {
+          timestamp = this.localMessages[c].list[0].sentTime;
+        }
+        return this.getRemoteMessages(conversation, { count, timestamp }).then(
+          () => {
+            // 已经存入本地数据,从本地数据拿出
+            return this.getLocalMessage(conversation, startTime, count);
+          }
+        );
+      }
+    }
+  }
+  /**
+   * 获取本地消息
+   * @param conversation 会话
+   * @param startTime 从什么时间开始获取
+   * @param count 获取数量
+   * @returns { list: IAReceivedMessage[], hasMore: boolean }
+   */
+  getLocalMessage(conversation, startTime = 0, count = 20) {
+    const c = getConversationKey(conversation);
+    const messages = this.localMessages[c];
+    // 直接获取全部
+    if (count === 0) {
+      return messages || { list: [], hasMore: true };
+    }
+    // 本地数据中不存在此会话的数据,hasmore标记为true,则要求调用方拉取一次远程历史
+    if (!messages) {
+      return { list: [], hasMore: true };
+    }
+    if (startTime === 0) {
+      startTime = Date.now();
+    }
+    let index = messages.list.findIndex((one) => {
+      return startTime <= one.sentTime;
+    });
+    let result = [];
+    if (index === 0) {
+      // 说明startTime小于第零个的sentTime,则直接用第零个的sentTime开始在服务器查找数据
+      return { list: [], hasMore: messages.hasMore };
+    } else {
+      if (index === -1) {
+        // 没有找到,说明starttime大于任何一个messages中的消息sentTime
+        index = messages.list.length;
+      }
+      result = messages.list.slice(Math.max(0, index - count), index);
+      if (result.length >= count && index - count > 0) {
+        // 通知业务层,本地是否还有更多的历史记录
+        // 假设此会话已经将历史记录全部拉取回本地,此时hasMore记录为false(没有更多的历史消息)
+        // 当切换回此会话时,下拉加载通知给业务层的会为false,造成判断错误。
+        return { list: result, hasMore: true };
+      }
+      // 本地数据不足,因此通知业务层的hasmore 为服务器返回
+      return { list: result, hasMore: messages.hasMore };
+    }
+  }
+  /**
+   * 获取远程消息
+   * @param conversation
+   * @param option
+   * @returns Promise<void>
+   */
+  getRemoteMessages(conversation, option) {
+    return getHistoryMessages(conversation, option).then((res) => {
+      if (res.code === 0) {
+        let list = res.data.list;
+        // 排除用户要自己接管的数据
+        if (
+          this.customIntercept &&
+          typeof this.customIntercept.interceptMessage === "function"
+        ) {
+          list = list.filter((one) => {
+            const isMatch = this.customIntercept.interceptMessage(one);
+            return !isMatch;
+          });
+        }
+        // 用户处理消息【供客户对消息进行加解密操作】
+        if (
+          this.customDisplayMessage &&
+          this.customDisplayMessage.willDisplayMessages &&
+          typeof this.customDisplayMessage.willDisplayMessages === "function"
+        ) {
+          list = list.map((message) => {
+            let content = this.customDisplayMessage.willDisplayMessages(
+              message.content,
+              message.conversationType,
+              message.targetId,
+              message.messageType
+            );
+            message.content = content;
+            return message;
+          });
+        }
+        this.setLocalMessages(list, res.data.hasMore);
+      }
+    });
+  }
+  /**
+   * 按sentTime升序存入
+   * @param messages IAReceivedMessage[] 要存入的消息列表
+   * @returns
+   */
+  setLocalMessages(messages = [], hasMore) {
+    const result = [];
+    if (!messages.length) {
+      return { new: result };
+    }
+    const willRemove = [];
+    const c =
+      messages[0].conversationType +
+      "&&" +
+      messages[0].targetId +
+      "&&" +
+      (messages[0].channelId || "");
+    if (this.localMessages[c]) {
+      if (hasMore !== undefined) {
+        this.localMessages[c].hasMore = hasMore;
+      }
+    } else {
+      // 不存数据的情况下,hasMore设定为true,可以防止丢消息
+      this.localMessages[c] = {
+        list: [],
+        hasMore: hasMore === undefined ? true : hasMore,
+      };
+    }
+    const list = this.localMessages[c].list;
+    messages.forEach((message) => {
+      let targetPosition = list.length;
+      // 收到对方的消息撤回通知,则在本地会话中清理要撤回的消息
+      if (
+        message.messageType === "RC:RcCmd" &&
+        message.messageDirection === MessageDirection.RECEIVE
+      ) {
+        const content = message.content;
+        if (content && content.messageUId) {
+          const removeMessage = this.deleteMessageByUId(
+            list,
+            content.messageUId
+          );
+          if (removeMessage) {
+            willRemove.push(removeMessage);
+          }
+        }
+      }
+      for (let i = 0; i < list.length; i++) {
+        const localMessage = list[i];
+        if (message.messageUId === localMessage.messageUId) {
+          // 防止消息重复
+          return;
+        } else if (message.sentTime <= localMessage.sentTime) {
+          targetPosition = i;
+          break;
+        }
+      }
+      // 返回当前的会话的更新消息
+      if (
+        this._currentConversation &&
+        isEqual(message, this._currentConversation)
+      ) {
+        result.push(message);
+      }
+      // 根据本地存储的已读响应时间戳判断单聊消息的已读未读状态。
+      const isPrivat = message.conversationType === ConversationType.PRIVATE;
+      const isSendUser = message.messageDirection === MessageDirection.SEND;
+      const isUnRead = message.sentTime > this._unReadTime || !this._unReadTime;
+      if (isPrivat && isSendUser && isUnRead) {
+        // 单聊,自己发送的消息
+        message.receivedStatus = ReceivedStatus.UNREAD;
+      }
+      // 将历史消息写入本地数据
+      this.localMessages[c].list.splice(targetPosition, 0, message);
+    });
+    result.sort((a, b) => {
+      return a.sentTime - b.sentTime;
+    });
+    return { new: result, remove: willRemove };
+  }
+  /**
+   * 撤回消息
+   * @param message
+   * @returns IAReceivedMessage | null
+   */
+  recallMessage(message) {
+    const key = getConversationKey(message);
+    const result = this.localMessages[key];
+    if (!result) {
+      return null;
+    }
+    const messages = result.list;
+    if (messages && messages.length) {
+      const index = messages.findIndex((m) => {
+        return m.messageUId === message.messageUId;
+      });
+      if (index !== -1) {
+        // messages[index].messageType += '__RECALL'
+        messages[index].messageType = "RC:RcCmd";
+        return messages[index];
+      }
+      return null;
+    }
+    return null;
+  }
+  /**
+   * 删除消息
+   * @param message
+   * @returns boolean
+   */
+  deleteMessage(message) {
+    const key = getConversationKey(message);
+    const result = this.localMessages[key];
+    if (!result) {
+      return false;
+    }
+    const messages = result.list;
+    if (messages && messages.length) {
+      const index = messages.findIndex((m) => {
+        return m.messageUId === message.messageUId;
+      });
+      if (index !== -1) {
+        logger.info(LogTagId.A_KIT_MSG_DELETE_O, {
+          index,
+          message: messages[index],
+        });
+        messages.splice(index, 1);
+        return true;
+      }
+      return false;
+    }
+    return false;
+  }
+  /**
+   * 根据消息UId删除本地消息
+   * @param list
+   * @param uid
+   * @returns
+   */
+  deleteMessageByUId(list, uid) {
+    const index = list.findIndex((one) => {
+      return one.messageUId === uid;
+    });
+    if (index !== -1) {
+      const message = list[index];
+      list.splice(index, 1);
+      logger.info(LogTagId.A_KIT_MSG_DELETE_O, { index, message: list[index] });
+      return message;
+    }
+  }
+  /**
+   * 插入消息
+   * @param message
+   * @returns void
+   */
+  insertMessage(message) {
+    const key = getConversationKey(message);
+    const result = this.localMessages[key];
+    if (!result) {
+      this.localMessages[key] = { list: [message], hasMore: true };
+      return;
+    }
+    const list = this.localMessages[key].list;
+    const index = list.findIndex((one) => one.sentTime > message.sentTime);
+    if (index === -1) {
+      list.push(message);
+    } else {
+      list.splice(index, 0, message);
+    }
+    logger.info(LogTagId.A_KIT_MSG_INSERT_O, { index, message });
+  }
+  /**
+   * 更新消息
+   * @param message
+   * @returns void
+   */
+  updateMessage(message) {
+    const key = getConversationKey(message);
+    const result = this.localMessages[key];
+    if (!result) {
+      this.insertMessage(message);
+      return;
+    }
+    const list = this.localMessages[key].list;
+    let index = -1;
+    // 首先考虑messageId,如果不存在messageId则再用messageUId来
+    if (message.messageId) {
+      index = list.findIndex((one) => one.messageId === message.messageId);
+    }
+    if (index === -1 && message.messageUId) {
+      index = list.findIndex((one) => one.messageUId === message.messageUId);
+    }
+    if (index === -1) {
+      this.insertMessage(message);
+      return;
+    }
+    for (const p in message) {
+      if (message[p] !== undefined) {
+        // logger.info(LogTagId.A_KIT_MSG_UPDATE_O, { old: list[index][p], new: message[p] })
+        list[index][p] = message[p];
+      }
+    }
+  }
+  /**
+   * 截取消息,保留最新的100条
+   * @returns void
+   */
+  cutMessages() {
+    const key = getConversationKey(this._currentConversation);
+    const result = this.localMessages[key];
+    if (!result) {
+      return;
+    }
+    if (result.list.length > 100) {
+      result.list.splice(0, result.list.length - 100);
+      result.hasMore = true;
+    }
+  }
+  /**
+   * disconnect 置空本地消息
+   */
+  clearLocalConversation() {
+    this.localMessages = {};
+  }
+}

+ 76 - 0
src/layout/components/imkit/service.js

@@ -0,0 +1,76 @@
+import { getConversationKey } from "./utils";
+let myProfileCache;
+const profileCache = {};
+const groupMembersCache = {};
+export default class Service {
+  constructor(config) {
+    this.config = config;
+  }
+  getUserProfile(id) {
+    if (!this.config.getUserProfile) {
+      return Promise.reject(new Error("Method getUserProfile is not defined!"));
+    }
+    if (myProfileCache) {
+      return Promise.resolve(myProfileCache);
+    }
+    return this.config.getUserProfile(id).then(res => {
+      myProfileCache = res;
+      return myProfileCache;
+    });
+  }
+  getConversationProfile(conversations) {
+    if (!this.config.getConversationProfile) {
+      return Promise.reject(
+        new Error("Method getConversationProfile is not defined!")
+      );
+    }
+    // 临时存放从缓存取出的数据
+    const temp = [];
+    for (let i = 0; i < conversations.length; i++) {
+      const conversation = conversations[i];
+      const key = getConversationKey(conversation);
+      const profile = profileCache[key];
+      if (profile) {
+        // 缓存中存在的数据,不再查询
+        conversations.splice(i, 1);
+        temp.push(profile);
+        i--;
+      }
+    }
+    return this.config.getConversationProfile(conversations).then(profiles => {
+      // 对数据进行缓存
+      profiles.forEach(profile => {
+        if (profile && profile.targetId && profile.conversationType) {
+          const key = getConversationKey(profile);
+          profileCache[key] = profile;
+        }
+      });
+      // 将之前的临时数据合并
+      return [...profiles, ...temp];
+    });
+  }
+  getGroupMembers(conversation) {
+    if (!this.config.getGroupMembers) {
+      return Promise.reject(
+        new Error("Method getGroupMembers is not defined!")
+      );
+    }
+
+    const key = getConversationKey(conversation);
+    if (groupMembersCache[key]) {
+      return Promise.resolve(groupMembersCache[key]);
+    }
+    return this.config.getGroupMembers(conversation).then(res => {
+      console.log(res, "res");
+      const key = getConversationKey(conversation);
+      if (res) {
+        groupMembersCache[key] = res;
+        return groupMembersCache[key];
+      }
+      return null;
+    });
+  }
+  clearMyProfileCache() {
+    myProfileCache = null;
+  }
+}

+ 71 - 0
src/layout/components/imkit/storage.js

@@ -0,0 +1,71 @@
+import { Languages } from './enum/languages';
+let storage;
+export const createStorage = (w, _appkey, _currentUserId) => {
+  const storageKey = `imkit_${_appkey}_${_currentUserId}`;
+  // if (!storage) {
+  storage = {
+    set(value, key) {
+      const val = JSON.stringify(value);
+      w.localStorage.setItem(`${storageKey}_${key}`, val);
+      if (value === undefined || value === null) {
+        w.localStorage.removeItem(`${storageKey}_${key}`);
+      }
+    },
+    get(key) {
+      let item;
+      try {
+        item = JSON.parse(w.localStorage.getItem(`${storageKey}_${key}`));
+      }
+      catch (e) {
+        item = null;
+      }
+      return item;
+    },
+    remove(key) {
+      w.localStorage.removeItem(`${storageKey}_${key}`);
+    }
+  };
+  // }
+  return storage;
+};
+export class ConversationStore {
+  constructor() {
+    this._conversationStorage = {};
+  }
+  init(_appkey, _currentUserId) {
+    this._kitStorage = createStorage(window, _appkey, _currentUserId);
+    this._conversationStorage = this._kitStorage.get('conversation') || {};
+  }
+  updateUnReadTime(time, type, targetId, channelId) {
+    const key = this.getConversationKey(type, targetId, channelId);
+    this._conversationStorage[key] = time;
+    this._kitStorage.set(this._conversationStorage, 'conversation');
+  }
+  getUnReadTime(type, targetId, channelId) {
+    const key = this.getConversationKey(type, targetId, channelId);
+    return this._conversationStorage[key] || 0;
+  }
+  get data() {
+    return this._conversationStorage;
+  }
+  getConversationKey(type, targetId, channelId) {
+    return `${channelId}_${type}_${targetId}`;
+  }
+}
+export class LangStore {
+  constructor() {
+    this._LangStore = null;
+  }
+  init(_appkey, _currentUserId) {
+    this._kitStorage = createStorage(window, _appkey, _currentUserId);
+    this._LangStore = this._kitStorage.get('lang') || null;
+  }
+  setLang(value) {
+    this._LangStore = value;
+    this._kitStorage.set(this._LangStore, 'lang');
+  }
+  getLang() {
+    let lang = this._LangStore && this._LangStore.lang ? this._LangStore.lang : Languages.ZH_CN;
+    return lang;
+  }
+}

+ 28 - 0
src/layout/components/imkit/utils.js

@@ -0,0 +1,28 @@
+export function isEqual(x, y) {
+  if ((x.channelId || '') === (y.channelId || '') &&
+    x.conversationType === y.conversationType &&
+    x.targetId === y.targetId) {
+    return true;
+  }
+  return false;
+}
+export function getConversationKey(conversation) {
+  return conversation.conversationType + '&&' +
+    conversation.targetId + '&&' + (conversation.channelId || '');
+}
+export function file2Base64(file) {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.readAsDataURL(file);
+    reader.onload = function (e) {
+      reader.onerror = null;
+      reader.onload = null;
+      resolve(e.target.result);
+    };
+    reader.onerror = function (e) {
+      reader.onerror = null;
+      reader.onload = null;
+      reject(e);
+    };
+  });
+}

+ 50 - 0
src/layout/components/imkit/utils/bscroll-hack.js

@@ -0,0 +1,50 @@
+var eventTypeMap = {
+    touchstart: 1,
+    touchmove: 1,
+    touchend: 1,
+    touchcancel: 1,
+    mousedown: 2,
+    mousemove: 2,
+    mouseup: 2,
+};
+
+function preventDefaultExceptionFn(el, exceptions) {
+    for (var i in exceptions) {
+        if (exceptions[i].test(el[i])) {
+            return true;
+        }
+    }
+    return false;
+}
+var tagExceptionFn = preventDefaultExceptionFn;
+
+const ActionsHandlerStart = function (e) {
+    var _eventType = eventTypeMap[e.type];
+    if (this.initiated && this.initiated !== _eventType) {
+        return;
+    }
+    this.setInitiated(_eventType);
+    // if textarea or other html tags in options.tagException is manipulated
+    // do not make bs scroll
+    if (tagExceptionFn(e.target, this.options.tagException)) {
+        this.setInitiated();
+        return;
+    }
+    // only allow mouse left button
+    if (_eventType === 2 /* Mouse */ && e.button !== 0 /* Left */) {
+        this.setInitiated(); // 防止按下右键进行拖拽
+        return;
+    }
+    if (this.hooks.trigger(this.hooks.eventTypes.beforeStart, e)) {
+        return;
+    }
+    this.beforeHandler(e, 'start');
+    var point = (e.touches ? e.touches[0] : e);
+    this.pointX = point.pageX;
+    this.pointY = point.pageY;
+    this.hooks.trigger(this.hooks.eventTypes.start, e);
+};
+
+export {
+    ActionsHandlerStart
+}

+ 50 - 0
src/layout/components/imkit/utils/mouse-wheel-hack.js

@@ -0,0 +1,50 @@
+import MouseWheel from "@better-scroll/mouse-wheel";
+
+MouseWheel.prototype.wheelMoveHandler = function (delta) {
+  var _this = this;
+  var _a = this.mouseWheelOpt, throttleTime = _a.throttleTime, dampingFactor = _a.dampingFactor;
+  if (throttleTime && this.wheelMoveTimer) {
+    this.deltaCache.push(delta);
+  }
+  else {
+    var cachedDelta = this.deltaCache.reduce(function (prev, current) {
+      return {
+        x: prev.x + current.x,
+        y: prev.y + current.y,
+      };
+    }, { x: 0, y: 0 });
+    this.cleanCache();
+    var _b = this.scroll.scroller, scrollBehaviorX = _b.scrollBehaviorX, scrollBehaviorY = _b.scrollBehaviorY;
+    scrollBehaviorX.setMovingDirection(-delta.directionX);
+    scrollBehaviorY.setMovingDirection(-delta.directionY);
+    scrollBehaviorX.setDirection(delta.x);
+    scrollBehaviorY.setDirection(delta.y);
+    // when out of boundary, perform a damping scroll
+    var newX = scrollBehaviorX.performDampingAlgorithm(Math.round(delta.x) + cachedDelta.x, dampingFactor);
+    var newY = scrollBehaviorY.performDampingAlgorithm(Math.round(delta.y) + cachedDelta.x, dampingFactor);
+    if (!this.scroll.trigger(this.scroll.eventTypes.mousewheelMove, {
+      x: newX,
+      y: newY,
+    })) {
+      var easeTime = this.getEaseTime();
+      if (newX !== this.scroll.x || newY !== this.scroll.y) {
+        // 如果是苹果触摸板,在加速度为负的情况下,会造成直接瞬间回到0位,视觉效果太差
+        if (Math.abs(this.scroll.y - newY) < 1) {
+          if (this.scroll.y > newY) {
+            newY = this.scroll.y - 1.01
+          } else {
+            newY = this.scroll.y + 1.01
+          }
+        }
+        this.scroll.scrollTo(newX, newY, easeTime);
+      }
+    }
+    if (throttleTime) {
+      this.wheelMoveTimer = window.setTimeout(function () {
+        _this.wheelMoveTimer = 0;
+      }, throttleTime);
+    }
+  }
+};
+
+export default MouseWheel;

+ 195 - 0
src/layout/components/imkit/utils/utils.js

@@ -0,0 +1,195 @@
+const fileHeader = {
+  "/9j": { type: 'image', suffix: "JPG" },
+  "iVB": { type: 'image', suffix: "PNG" },
+  "Qk0": { type: 'image', suffix: "BMP" },
+  "SUk": { type: 'image', suffix: "TIFF" },
+  "JVB": { type: 'image', suffix: "PDF" },
+  "UEs": { type: 'image', suffix: "OFD" },
+  "IyF": { type: 'audio', suffix: "AMR" }
+};
+/**
+ * 根据base64的前三个字符判断文件格式
+ * @param base64
+ * @returns base64 string
+ */
+export function addBase64head(base64) {
+  if (base64.startsWith('data:/') || base64.startsWith('http')) {
+    return base64;
+  }
+  const head = base64.substr(0, 3);
+  if (fileHeader[head]) {
+    return `data:${fileHeader[head].type}/${fileHeader[head].suffix.toLowerCase()};base64,${base64}`;
+  }
+  // 直接返回
+  return base64;
+}
+// 格式化时间
+export function formatTime(time, format, locale) {
+  format = Object.assign({
+    year: locale.message.year,
+    month: locale.message.month,
+    day: locale.message.day,
+    hour: locale.message.hour,
+    minute: locale.message.minute
+  }, format || {});
+  if (!time) {
+    return null;
+  }
+  const date = new Date(time);
+  const year = date.getFullYear();
+  const month = date.getMonth() + 1;
+  const day = date.getDate();
+  let hour = date.getHours() + "";
+  if (hour.length < 2) {
+    hour = '0' + hour;
+  }
+  let minute = date.getMinutes() + "";
+  if (minute.length < 2) {
+    minute = '0' + minute;
+  }
+  const now = new Date();
+  const dValue = judgeDay(date);
+  if (dValue === -1) {
+    // let yesterday = locale ? locale.conversation.yesterday : '';
+    let yesterday = locale.conversation.yesterday;
+    // return `${yesterday} ${hour}${format.hour}${minute}${format.minute}`;
+    // return `${yesterday} ${hour}${format.hour}${minute}${format.minute}`;
+    return `${yesterday}`;
+  }
+  if (now.getFullYear() === year) {
+    if (now.toDateString() === date.toDateString()) {
+      return `${hour}:${minute}`;
+    }
+    return `${month}/${day}`;
+  }
+  return locale.area === 'zh_CN' ? `${year}/${month}/${day}` : `${month}/${day}/${year}`;
+  // else if (dValue === -2) {
+  //   // let before = locale ? locale.conversation.before : '';
+  //   let before = locale.conversation.before;
+  //   return `${before} ${hour}${format.hour}${minute}${format.minute}`;
+  // }
+  // 同年
+  // if (now.getFullYear() === year) {
+  //   // 同月
+  //   if (now.getMonth() + 1 === month) {
+  //     // 同天
+  //     if (now.toDateString() === date.toDateString()) {
+  //       return `${hour}${format.hour}${minute}${format.minute}`;
+  //     } else {
+  //       return `${month}${format.month}${day}${format.day} ${hour}${format.hour}${minute}${format.minute}`;
+  //     }
+  //   } else {
+  //     return `${month}${format.month}${day}${format.day} ${hour}${format.hour}${minute}${format.minute}`;
+  //   }
+  // }
+  // return `${year}${format.year}${month}${format.month}`;
+}
+function judgeDay(sourceDate, targetDate = new Date()) {
+  var year = sourceDate.getFullYear();
+  var month = sourceDate.getMonth() + 1;
+  var day = sourceDate.getDate();
+  var d1 = new Date(year + '/' + month + '/' + day).getTime();
+  var y = targetDate.getFullYear();
+  var m = targetDate.getMonth() + 1;
+  var d = targetDate.getDate();
+  var d2 = new Date(y + '/' + m + '/' + d).getTime();
+  var iday = (d1 - d2) / 1000 / 60 / 60 / 24;
+  return iday;
+}
+export function getCursortPosition(element) {
+  var caretOffset = 0;
+  var doc = element.ownerDocument || element.document;
+  var win = doc.defaultView || doc.parentWindow;
+  var sel;
+  if (typeof win.getSelection != "undefined") { //谷歌、火狐
+    sel = win.getSelection();
+    if (sel.rangeCount > 0) { //选中的区域
+      var range = win.getSelection().getRangeAt(0);
+      var preCaretRange = range.cloneRange(); //克隆一个选中区域
+      preCaretRange.selectNodeContents(element); //设置选中区域的节点内容为当前节点
+      preCaretRange.setEnd(range.endContainer, range.endOffset); //重置选中区域的结束位置
+      caretOffset = preCaretRange.toString().length;
+    }
+  }
+  else if ((sel = doc.selection) && sel.type != "Control") { //IE
+    var textRange = sel.createRange();
+    var preCaretTextRange = doc.body.createTextRange();
+    preCaretTextRange.moveToElementText(element);
+    preCaretTextRange.setEndPoint("EndToEnd", textRange);
+    caretOffset = preCaretTextRange.text.length;
+  }
+  return caretOffset;
+}
+/**
+ * 获取光标前的文本
+ * @param containerEl
+ * @returns
+ */
+export function getTextBeforeCursor(containerEl) {
+  var precedingChar = '', sel, range, precedingRange;
+  if (window.getSelection) {
+    sel = window.getSelection();
+    if (sel.rangeCount > 0) {
+      range = sel.getRangeAt(0).cloneRange();
+      range.collapse(true);
+      range.setStart(containerEl, 0);
+      precedingChar = range.cloneContents();
+    }
+  }
+  else if ((sel = document.selection)) {
+    range = sel.createRange();
+    precedingRange = range.duplicate();
+    precedingRange.moveToElementText(containerEl);
+    precedingRange.setEndPoint("EndToStart", range);
+    precedingChar = precedingRange.htmlText;
+  }
+  return [precedingChar, range];
+}
+export function delHtmlTag(str) {
+  //正则去掉所有的html标记
+  return str.replace(/<[^>]+>/g, '');
+}
+export function deepClone(target) {
+  let result;
+  if (typeof target === 'object') {
+    if (Array.isArray(target)) {
+      result = [];
+      for (const i in target) {
+        result.push(deepClone(target[i]));
+      }
+    }
+    else if (target === null) {
+      result = null;
+    }
+    else if (target instanceof RegExp) {
+      result = target;
+    }
+    else {
+      result = {};
+      for (const i in target) {
+        result[i] = deepClone(target[i]);
+      }
+    }
+  }
+  else {
+    result = target;
+  }
+  return result;
+}
+export function formatTextMessage(content) {
+  // const urlReg = new RegExp('(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]', 'g');
+  const urlReg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/g;
+  // 简单的过滤
+  content = content.replace('<', '&lt;');
+  content = content.replace('>', '&gt;');
+  if (urlReg.test(content)) {
+    content = content.replace(urlReg, a => {
+      let prefix = '';
+      if (a.indexOf('http:') < 0 && a.indexOf('https:') < 0) {
+        prefix = '//';
+      }
+      return `<a target="_blank" class="url-wrap" href="${prefix}${a}">${a}</a>`;
+    });
+  }
+  return content;
+}

+ 64 - 0
src/layout/components/imkit/zh_CN.js

@@ -0,0 +1,64 @@
+export default {
+  area: 'zh_CN',
+  lang: '中文',
+  // 会话列表
+  conversation: {
+    loading: '正在加载列表',
+    forMore: '上拉获取更多',
+    noData: '当前无会话',
+    draft: '草稿',
+    atMe: '有人@我',
+    file: '文件',
+    img: '图片',
+    location: '位置消息',
+    video: '短视频',
+    audio: '语音消息',
+    quote: '引用消息',
+    yesterday: '昨天',
+    before: '前天',
+    top: '取消置顶',
+    noTop: '置顶',
+    inform: '允许消息通知',
+    noInform: '消息免打扰',
+    delete: '删除',
+    read: '已读',
+    unread: '未读',
+    recall: '撤回了一条消息',
+    you: '你'
+  },
+  //消息列表
+  message: {
+    history: '下拉获取历史消息',
+    noHistory: '没有更多历史消息了',
+    loading: '加载中...',
+    completed: '加载完毕',
+    noData: '新的一天,新的开始',
+    year: '年',
+    month: '月',
+    day: '日',
+    hour: '点',
+    minute: '分',
+    copy: '复制',
+    quote: '引用',
+    recall: '撤回',
+    delete: '删除',
+    msgRecall: '撤回了一条消息',
+    you: '你',
+    opposite: '对方',
+    file: '文件',
+    location: '位置',
+    nonsupport: '当前版本不支持查看此消息',
+    forward: '转发'
+  },
+  //消息编辑器
+  messageEdit: {
+    send: '发送',
+    image: '图片',
+    picError: '图片格式有误',
+    file: '文件'
+  },
+  other: {
+    unKnowMessage: '未知消息',
+    all: '所有人'
+  }
+}

+ 382 - 0
src/layout/components/message.js

@@ -0,0 +1,382 @@
+import {
+  getHistoryMessages,
+  MessageDirection,
+  ReceivedStatus,
+  ConversationType,
+} from "@rongcloud/imlib-next";
+import { logger } from "@rongcloud/engine";
+import { LogTagId } from "./enum/logEnums";
+import { getConversationKey, isEqual } from "./utils";
+export default class MessageManager {
+  constructor() {
+    /**
+     * 最近的消息列表
+     */
+    this.localMessages = {};
+  }
+  /**
+   * 设置当前会话
+   */
+  set currentConversation(conversation) {
+    this._currentConversation = conversation;
+  }
+  /**
+   * 获取当前会话
+   */
+  get currentConversation() {
+    return this._currentConversation;
+  }
+  /**
+   * 获取消息,如果本地消息不足,则从服务端拉取
+   * @param conversation 会话
+   * @param start 开始时间,默认从最新一条的时间开始
+   * @param count 获取数量,默认20条
+   * @returns
+   */
+  getMessages(conversation, startTime = 0, count = 20, unReadTime) {
+    count = Math.min(20, Math.max(1, count));
+    this._unReadTime = unReadTime;
+    const result = this.getLocalMessage(conversation, startTime, count);
+    // 拿到本地消息匹配消息是否已读
+    result.list &&
+      result.list.forEach((message) => {
+        const isPrivat = message.conversationType === ConversationType.PRIVATE;
+        const isSendUser = message.messageDirection === MessageDirection.SEND;
+        const isUnRead = message.sentTime <= this._unReadTime;
+        if (isPrivat && isSendUser && isUnRead) {
+          // 单聊,自己发送的消息
+          message.receivedStatus = ReceivedStatus.READ;
+        }
+      });
+    if (result.list.length >= count) {
+      // 消息数量能满足count
+      return Promise.resolve(result);
+    } else {
+      if (result.hasMore === false) {
+        // 服务器没有更多历史消息
+        return Promise.resolve(result);
+      } else {
+        const c = getConversationKey(conversation);
+        let timestamp = 0;
+        if (this.localMessages[c] && this.localMessages[c].list.length > 0) {
+          timestamp = this.localMessages[c].list[0].sentTime;
+        }
+        return this.getRemoteMessages(conversation, { count, timestamp }).then(
+          () => {
+            // 已经存入本地数据,从本地数据拿出
+            return this.getLocalMessage(conversation, startTime, count);
+          }
+        );
+      }
+    }
+  }
+  /**
+   * 获取本地消息
+   * @param conversation 会话
+   * @param startTime 从什么时间开始获取
+   * @param count 获取数量
+   * @returns { list: IAReceivedMessage[], hasMore: boolean }
+   */
+  getLocalMessage(conversation, startTime = 0, count = 20) {
+    const c = getConversationKey(conversation);
+    const messages = this.localMessages[c];
+    // 直接获取全部
+    if (count === 0) {
+      return messages || { list: [], hasMore: true };
+    }
+    // 本地数据中不存在此会话的数据,hasmore标记为true,则要求调用方拉取一次远程历史
+    if (!messages) {
+      return { list: [], hasMore: true };
+    }
+    if (startTime === 0) {
+      startTime = Date.now();
+    }
+    let index = messages.list.findIndex((one) => {
+      return startTime <= one.sentTime;
+    });
+    let result = [];
+    if (index === 0) {
+      // 说明startTime小于第零个的sentTime,则直接用第零个的sentTime开始在服务器查找数据
+      return { list: [], hasMore: messages.hasMore };
+    } else {
+      if (index === -1) {
+        // 没有找到,说明starttime大于任何一个messages中的消息sentTime
+        index = messages.list.length;
+      }
+      result = messages.list.slice(Math.max(0, index - count), index);
+      if (result.length >= count && index - count > 0) {
+        // 通知业务层,本地是否还有更多的历史记录
+        // 假设此会话已经将历史记录全部拉取回本地,此时hasMore记录为false(没有更多的历史消息)
+        // 当切换回此会话时,下拉加载通知给业务层的会为false,造成判断错误。
+        return { list: result, hasMore: true };
+      }
+      // 本地数据不足,因此通知业务层的hasmore 为服务器返回
+      return { list: result, hasMore: messages.hasMore };
+    }
+  }
+  /**
+   * 获取远程消息
+   * @param conversation
+   * @param option
+   * @returns Promise<void>
+   */
+  getRemoteMessages(conversation, option) {
+    return getHistoryMessages(conversation, option).then((res) => {
+      if (res.code === 0) {
+        let list = res.data.list;
+        // 排除用户要自己接管的数据
+        if (
+          this.customIntercept &&
+          typeof this.customIntercept.interceptMessage === "function"
+        ) {
+          list = list.filter((one) => {
+            const isMatch = this.customIntercept.interceptMessage(one);
+            return !isMatch;
+          });
+        }
+        // 用户处理消息【供客户对消息进行加解密操作】
+        if (
+          this.customDisplayMessage &&
+          this.customDisplayMessage.willDisplayMessages &&
+          typeof this.customDisplayMessage.willDisplayMessages === "function"
+        ) {
+          list = list.map((message) => {
+            let content = this.customDisplayMessage.willDisplayMessages(
+              message.content,
+              message.conversationType,
+              message.targetId,
+              message.messageType
+            );
+            message.content = content;
+            return message;
+          });
+        }
+        this.setLocalMessages(list, res.data.hasMore);
+      }
+    });
+  }
+  /**
+   * 按sentTime升序存入
+   * @param messages IAReceivedMessage[] 要存入的消息列表
+   * @returns
+   */
+  setLocalMessages(messages = [], hasMore) {
+    const result = [];
+    if (!messages.length) {
+      return { new: result };
+    }
+    const willRemove = [];
+    const c =
+      messages[0].conversationType +
+      "&&" +
+      messages[0].targetId +
+      "&&" +
+      (messages[0].channelId || "");
+    if (this.localMessages[c]) {
+      if (hasMore !== undefined) {
+        this.localMessages[c].hasMore = hasMore;
+      }
+    } else {
+      // 不存数据的情况下,hasMore设定为true,可以防止丢消息
+      this.localMessages[c] = {
+        list: [],
+        hasMore: hasMore === undefined ? true : hasMore,
+      };
+    }
+    const list = this.localMessages[c].list;
+    messages.forEach((message) => {
+      let targetPosition = list.length;
+      // 收到对方的消息撤回通知,则在本地会话中清理要撤回的消息
+      if (
+        message.messageType === "RC:RcCmd" &&
+        message.messageDirection === MessageDirection.RECEIVE
+      ) {
+        const content = message.content;
+        if (content && content.messageUId) {
+          const removeMessage = this.deleteMessageByUId(
+            list,
+            content.messageUId
+          );
+          if (removeMessage) {
+            willRemove.push(removeMessage);
+          }
+        }
+      }
+      for (let i = 0; i < list.length; i++) {
+        const localMessage = list[i];
+        if (message.messageUId === localMessage.messageUId) {
+          // 防止消息重复
+          return;
+        } else if (message.sentTime <= localMessage.sentTime) {
+          targetPosition = i;
+          break;
+        }
+      }
+      // 返回当前的会话的更新消息
+      if (
+        this._currentConversation &&
+        isEqual(message, this._currentConversation)
+      ) {
+        result.push(message);
+      }
+      // 根据本地存储的已读响应时间戳判断单聊消息的已读未读状态。
+      const isPrivat = message.conversationType === ConversationType.PRIVATE;
+      const isSendUser = message.messageDirection === MessageDirection.SEND;
+      const isUnRead = message.sentTime > this._unReadTime || !this._unReadTime;
+      if (isPrivat && isSendUser && isUnRead) {
+        // 单聊,自己发送的消息
+        message.receivedStatus = ReceivedStatus.UNREAD;
+      }
+      // 将历史消息写入本地数据
+      this.localMessages[c].list.splice(targetPosition, 0, message);
+    });
+    result.sort((a, b) => {
+      return a.sentTime - b.sentTime;
+    });
+    return { new: result, remove: willRemove };
+  }
+  /**
+   * 撤回消息
+   * @param message
+   * @returns IAReceivedMessage | null
+   */
+  recallMessage(message) {
+    const key = getConversationKey(message);
+    const result = this.localMessages[key];
+    if (!result) {
+      return null;
+    }
+    const messages = result.list;
+    if (messages && messages.length) {
+      const index = messages.findIndex((m) => {
+        return m.messageUId === message.messageUId;
+      });
+      if (index !== -1) {
+        // messages[index].messageType += '__RECALL'
+        messages[index].messageType = "RC:RcCmd";
+        return messages[index];
+      }
+      return null;
+    }
+    return null;
+  }
+  /**
+   * 删除消息
+   * @param message
+   * @returns boolean
+   */
+  deleteMessage(message) {
+    const key = getConversationKey(message);
+    const result = this.localMessages[key];
+    if (!result) {
+      return false;
+    }
+    const messages = result.list;
+    if (messages && messages.length) {
+      const index = messages.findIndex((m) => {
+        return m.messageUId === message.messageUId;
+      });
+      if (index !== -1) {
+        logger.info(LogTagId.A_KIT_MSG_DELETE_O, {
+          index,
+          message: messages[index],
+        });
+        messages.splice(index, 1);
+        return true;
+      }
+      return false;
+    }
+    return false;
+  }
+  /**
+   * 根据消息UId删除本地消息
+   * @param list
+   * @param uid
+   * @returns
+   */
+  deleteMessageByUId(list, uid) {
+    const index = list.findIndex((one) => {
+      return one.messageUId === uid;
+    });
+    if (index !== -1) {
+      const message = list[index];
+      list.splice(index, 1);
+      logger.info(LogTagId.A_KIT_MSG_DELETE_O, { index, message: list[index] });
+      return message;
+    }
+  }
+  /**
+   * 插入消息
+   * @param message
+   * @returns void
+   */
+  insertMessage(message) {
+    const key = getConversationKey(message);
+    const result = this.localMessages[key];
+    if (!result) {
+      this.localMessages[key] = { list: [message], hasMore: true };
+      return;
+    }
+    const list = this.localMessages[key].list;
+    const index = list.findIndex((one) => one.sentTime > message.sentTime);
+    if (index === -1) {
+      list.push(message);
+    } else {
+      list.splice(index, 0, message);
+    }
+    logger.info(LogTagId.A_KIT_MSG_INSERT_O, { index, message });
+  }
+  /**
+   * 更新消息
+   * @param message
+   * @returns void
+   */
+  updateMessage(message) {
+    const key = getConversationKey(message);
+    const result = this.localMessages[key];
+    if (!result) {
+      this.insertMessage(message);
+      return;
+    }
+    const list = this.localMessages[key].list;
+    let index = -1;
+    // 首先考虑messageId,如果不存在messageId则再用messageUId来
+    if (message.messageId) {
+      index = list.findIndex((one) => one.messageId === message.messageId);
+    }
+    if (index === -1 && message.messageUId) {
+      index = list.findIndex((one) => one.messageUId === message.messageUId);
+    }
+    if (index === -1) {
+      this.insertMessage(message);
+      return;
+    }
+    for (const p in message) {
+      if (message[p] !== undefined) {
+        // logger.info(LogTagId.A_KIT_MSG_UPDATE_O, { old: list[index][p], new: message[p] })
+        list[index][p] = message[p];
+      }
+    }
+  }
+  /**
+   * 截取消息,保留最新的100条
+   * @returns void
+   */
+  cutMessages() {
+    const key = getConversationKey(this._currentConversation);
+    const result = this.localMessages[key];
+    if (!result) {
+      return;
+    }
+    if (result.list.length > 100) {
+      result.list.splice(0, result.list.length - 100);
+      result.hasMore = true;
+    }
+  }
+  /**
+   * disconnect 置空本地消息
+   */
+  clearLocalConversation() {
+    this.localMessages = {};
+  }
+}

+ 84 - 0
src/layout/components/modal/api.js

@@ -0,0 +1,84 @@
+import request2 from "@/utils/request2";
+let api = "/api-web";
+
+export function operationLog(data) {
+  return request2({
+    url: api + `/operationLog/save`,
+    method: "GET",
+    params: data,
+    data: {}
+  });
+}
+
+// /api-student/imGroup/queryGroupDetail. 群信息
+// /api-student/imGroup/queryGroupMemberDetail 群成员的
+// /api-student/imGroup/queryFriendDetail 获取单人的
+// 查询群详情
+export function queryGroupDetail(data) {
+  return request2({
+    hideLoading: true,
+    url: `/api-web/imGroup/queryGroupDetail`,
+    method: "GET",
+    params: data
+  });
+}
+
+// 查询群成员详情
+export function queryGroupMemberDetail(data) {
+  return request2({
+    hideLoading: true,
+    url: `/api-web/imGroup/queryGroupMemberDetail`,
+    method: "GET",
+    params: data
+  });
+}
+
+// 查询好友详情
+export function queryFriendDetail(data) {
+  return request2({
+    hideLoading: true,
+    url: `/api-web/imGroup/queryFriendDetail`,
+    method: "GET",
+    params: data
+  });
+}
+
+// 获取群聊、用户详情
+export function imGroupQueryDetail(data) {
+  return request2({
+    hideLoading: true,
+    url: `/api-web/imGroup/queryDetail`,
+    method: "POST",
+    data: data
+  });
+}
+
+// 查询群成员列表
+export function queryGroupMemberList(data) {
+  return request2({
+    hideLoading: true,
+    url: `/api-web/imGroup/queryGroupMemberList`,
+    method: "get",
+    params: data
+  });
+}
+
+// 查询好友列表
+export function queryFriendList(data) {
+  return request2({
+    hideLoading: true,
+    url: `/api-web/imGroup/queryFriendList`,
+    method: "get",
+    params: data
+  });
+}
+
+// 查询群列表
+export function queryGroupList(data) {
+  return request2({
+    hideLoading: true,
+    url: `/api-web/imGroup/queryGroupList`,
+    method: "get",
+    params: data
+  });
+}

+ 133 - 0
src/layout/components/modal/chat-model-ui.vue

@@ -0,0 +1,133 @@
+<template>
+  <div class="chat-message" v-if="isConnect">
+    <!-- 会话列表界面 -->
+    <!-- <div class="chat-conversation">
+      <conversation-list ref="conversationList" base-size="6px" />
+    </div>
+    <div class="chat-container">
+      <div class="chat-massage-list">
+        <message-list ref="messageList" base-size="6px"></message-list>
+      </div>
+      <div class="chat-massage-editor">
+        <message-editor ref="messageEditor" base-size="6px"></message-editor>
+      </div>
+    </div> -->
+    <el-container>
+      <el-aside width="200px">
+        <conversation-list ref="conversationList" base-size="6px" />
+      </el-aside>
+      <el-container>
+        <el-main>
+          <message-list ref="messageList" base-size="6px"></message-list>
+        </el-main>
+        <el-footer>
+          <message-editor ref="messageEditor" base-size="6px"></message-editor>
+        </el-footer>
+      </el-container>
+    </el-container>
+  </div>
+</template>
+
+<script>
+import { imkit, CoreEvent } from "@rongcloud/imkit";
+// import "./chat.js";
+// import { imkit } from "@rongcloud/imkit";
+import { custom_service } from "./chat.js";
+// 接入时需要将 '请更换您应用的 appkey' 替换为您的应用的 appkey
+let libOption = { appkey: "c9kqb3rdc451j" };
+
+// 初始化 SDK
+imkit.init({
+  service: custom_service,
+  libOption: libOption,
+  conversationPullCount: 20
+});
+
+export default {
+  data() {
+    return {
+      show: false,
+      isConnect: false // 加载中
+    };
+  },
+  mounted() {
+    console.log(this.$store.state.user.imToken);
+    imkit.connect(this.$store.state.user.imToken).then(res => {
+      console.log("连接成功", res);
+      // 加载会话列表 CoreEvent 可通过 import { CoreEvent } from '@rongcloud/imkit' 获取
+      imkit.emit(CoreEvent.CONVERSATION, true);
+    });
+    const messageList = this.$refs.messageList;
+    //添加点击消息触发监听
+    messageList.addEventListener("tapMessage", this.handleTapMessage);
+    const conversationList = this.$refs.conversationList;
+    //添加点击会话监听
+    conversationList.addEventListener(
+      "tapConversation",
+      this.handleTapConversation //回掉处理函数
+    );
+    //添加删除会话监听
+    conversationList.addEventListener(
+      "deleteConversation",
+      this.handleDeleteConversation //回掉处理函数
+    );
+  },
+  beforeUnmount() {
+    // 注意:需要 removeEventListener 防止多次绑定造成异常
+    const messageList = this.$refs.messageList;
+    messageList.removeEventListener("tapMessage", this.handleTapMessage);
+
+    const conversationList = this.$refs.conversationList;
+    conversationList.removeEventListener(
+      "tapConversation",
+      this.handleTapConversation
+    );
+    conversationList.removeEventListener(
+      "deleteConversation",
+      this.handleDeleteConversation
+    );
+  },
+  methods: {
+    handleTapMessage(e) {
+      const data = e.detail;
+      // 处理点击查看大图或文件消息下载等功能
+      console.log("点击消息触发监听:", data);
+    },
+    handleTapConversation() {
+      //处理点击会话后的操作
+      console.info("处理点击会话后的操作");
+    },
+    handleDeleteConversation() {
+      //处理删除会话后的操作
+      console.info("处理点击会话后的操作");
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.chat-message {
+  height: 100%;
+  display: flex;
+}
+
+.chat-conversation {
+  float: left;
+  width: 20vw;
+  height: 100%;
+  overflow: hidden;
+}
+.chat-container {
+  width: calc(100% - 20vw);
+}
+
+.chat-massage-list {
+  height: calc(100% - 150px);
+}
+.chat-massage-editor {
+  height: 150px;
+}
+/deep/.no-data::before {
+  background-size: cover;
+}
+</style>

+ 206 - 0
src/layout/components/modal/chat-model.vue

@@ -0,0 +1,206 @@
+<template>
+  <!-- v-if="isConnect" -->
+  <div class="chat-message">
+    <!-- 会话列表界面 -->
+    <div class="chat-header">
+      <i class="el-icon-close" @click="$listeners.close()"></i>
+
+      <div class="header-card">
+        <el-tooltip
+          effect="dark"
+          content="会话"
+          placement="bottom"
+          :open-delay="500"
+        >
+          <i
+            class="el-icon-chat-line-round"
+            :class="active === 'conversation' && 'active'"
+            @click="active = 'conversation'"
+          ></i>
+        </el-tooltip>
+        <el-tooltip
+          effect="dark"
+          content="联系人"
+          placement="bottom"
+          :open-delay="500"
+        >
+          <i
+            class="el-icon-user"
+            :class="active === 'contacts' && 'active'"
+            @click="active = 'contacts'"
+          ></i>
+        </el-tooltip>
+        <el-tooltip
+          effect="dark"
+          content="班级"
+          placement="bottom"
+          :open-delay="500"
+        >
+          <i
+            class="el-icon-menu"
+            :class="active === 'class' && 'active'"
+            @click="active = 'class'"
+          ></i>
+        </el-tooltip>
+      </div>
+    </div>
+    <el-container style="margin-top: 56px">
+      <el-aside width="280px" style="border-right: 1px solid #f7f7f7;">
+        <conversation-list
+          ref="conversationList"
+          v-show="active === 'conversation'"
+        />
+        <contacts-list ref="contactsList" v-show="active === 'contacts'" />
+        <class-list ref="classList" v-show="active === 'class'" />
+      </el-aside>
+      <!-- 判断是否有会话 -->
+      <el-container v-show="currentConversation.targetId">
+        <el-main style="padding: 0" id="el-main">
+          <message-list ref="messageList"></message-list>
+        </el-main>
+        <el-footer
+          style="height: auto; border-top: 1px solid #f7f7f7; padding: 0 20px;"
+        >
+          <message-editor ref="messageEditor"></message-editor>
+        </el-footer>
+      </el-container>
+      <el-empty
+        v-if="!currentConversation.targetId"
+        :image="emptyImg"
+        style="width: 100%;height: 100%"
+        description=" "
+      ></el-empty>
+    </el-container>
+  </div>
+</template>
+
+<script>
+import ConversationList from "../components/conversation-list.vue";
+import MessageList from "../components/message-list.vue";
+import MessageEditor from "../components/message-editor.vue";
+import ContactsList from "../components/contacts-list.vue";
+import ClassList from "../components/class-list.vue";
+import { core, CoreEvent } from "../imkit";
+import * as RongIMLib from "@rongcloud/imlib-next";
+import { custom_service } from "./chat.js";
+// 接入时需要将 '请更换您应用的 appkey' 替换为您的应用的 appkey
+let libOption = { appkey: "c9kqb3rdc451j" };
+
+// 初始化 SDK
+core.init({
+  service: custom_service,
+  libOption: libOption,
+  conversationPullCount: 20
+});
+
+export default {
+  components: {
+    ConversationList,
+    MessageList,
+    MessageEditor,
+    ContactsList,
+    ClassList
+  },
+  data() {
+    return {
+      emptyImg: require("../imkit/images/no-conversation.svg"),
+      active: "class", // 会话 conversation; 联系人 contacts; 班级 class
+      isConnect: false, // 连接中
+      currentConversation: {
+        channelId: "",
+        conversationType: 0,
+        targetId: ""
+      }
+    };
+  },
+  mounted() {
+    core.connect(this.$store.state.user.imToken).then(res => {
+      if (res.code === RongIMLib.ErrorCode.SUCCESS) {
+        console.log("链接成功, 链接用户 id 为: ", res.data.userId);
+        this.isConnect = true;
+      } else {
+        console.warn("链接失败, code:", res.code);
+      }
+    });
+    core.on(CoreEvent.SWITCH_CONVERSATION, this.switchConversation);
+
+    // 注册消息监听器
+    // core.on("tapConversation", function(e) {
+    //   console.log("tapConversation", e);
+    // });
+  },
+  methods: {
+    switchConversation(conversation) {
+      console.log(conversation, "switchConversation");
+      this.currentConversation = conversation;
+    }
+  },
+  destoryed() {
+    core.off(CoreEvent.SWITCH_CONVERSATION, this.switchConversation);
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.chat-message {
+  height: 100%;
+  display: flex;
+}
+
+.chat-header {
+  position: absolute;
+  left: 0;
+  right: 0;
+  background: #f7f7f7;
+  width: 100%;
+  height: 56px;
+
+  .el-icon-close {
+    position: absolute;
+    top: 0;
+    right: 12px;
+    line-height: 56px;
+    font-size: 18px;
+    font-weight: 600;
+    text-align: center;
+    color: #333;
+    cursor: pointer;
+  }
+
+  .header-card {
+    cursor: pointer;
+    line-height: 56px;
+    text-align: center;
+    font-size: 30px;
+    color: #333;
+    i {
+      margin: 0 8px;
+
+      &:hover,
+      &.active {
+        color: #00a79d;
+      }
+    }
+  }
+}
+
+.chat-conversation {
+  float: left;
+  width: 20vw;
+  height: 100%;
+  overflow: hidden;
+}
+.chat-container {
+  width: calc(100% - 20vw);
+}
+
+.chat-massage-list {
+  height: calc(100% - 150px);
+}
+.chat-massage-editor {
+  height: 150px;
+}
+/deep/.no-data::before {
+  background-size: cover;
+}
+</style>

+ 121 - 0
src/layout/components/modal/chat.js

@@ -0,0 +1,121 @@
+import {
+  queryFriendDetail,
+  queryGroupDetail,
+  imGroupQueryDetail,
+  queryGroupMemberList
+} from "./api";
+import groupLogo from "../images/group_logo.png";
+import teacherLogo from "../images/teacher_logo.png";
+import { ConversationType, MessageType } from "@rongcloud/engine"; //
+
+const getInitData = async list => {
+  try {
+    const tempList = [];
+    list.forEach(item => {
+      tempList.push({
+        id: item.targetId,
+        type: item.conversationType
+      });
+    });
+    const group = await imGroupQueryDetail(tempList);
+    const tempGroup = group.data || [];
+    list.forEach(item => {
+      const findItem = tempGroup.find(item2 => item2.id === item.targetId);
+
+      if (item == ConversationType.GROUP) {
+        item.memberCount = 0; // memberCount 为群成员数量
+      }
+      if (findItem) {
+        item.name = findItem.name;
+        item.portraitUri =
+          findItem.avatar || item.conversationType === 1
+            ? teacherLogo
+            : groupLogo;
+      } else {
+        item.name = "";
+        item.portraitUri =
+          item.conversationType === 1 ? teacherLogo : groupLogo;
+      }
+    });
+    return list;
+  } catch {
+    //
+    return [];
+  }
+};
+
+export const custom_service = {
+  // 获取用户详情
+  getUserProfile: async userId => {
+    // 需要通过 userId 向应用服务器获取 user 信息,拼接成如下格式
+    // 注意:userInfo 的 Key 不可修改
+    console.log(userId, "userId");
+    const userInfo = {
+      id: userId,
+      name: "用户姓名",
+      portraitUri: "用户头像 URI"
+    };
+    try {
+      const res = await queryFriendDetail({ userId });
+      // console.log(res, "res");
+      userInfo.name = res.data.friend.username;
+      userInfo.portraitUri = res.data.friend.avatar || teacherLogo;
+    } catch {
+      //
+    }
+    return Promise.resolve(userInfo);
+  },
+  // 获取会话详情
+  getConversationProfile: async conversations => {
+    // SDK 返回 conversations 为会话列表,可根据返回的 conversations 向应用服务器请求会话详情信息。
+    // 请根据具体 conversation 信息匹配 name、portraitUri 拼接到 converationInfo 信息中。
+    // console.log(conversations, "conversations");
+    const promises = [];
+
+    const tempConversations = await getInitData(conversations);
+    console.log(conversations, tempConversations, "tempConversations");
+
+    // conversations.forEach(conversation => {
+    //   const converationInfo = {
+    //     ...conversation,
+    //     name: conversation.name || `会话名称`,
+    //     portraitUri:
+    //       conversation.portraitUri || conversation.conversationType === 1
+    //         ? teacherLogo
+    //         : groupLogo
+    //   };
+    //   if (conversation == ConversationType.GROUP) {
+    //     converationInfo.memberCount = 0; // memberCount 为群成员数量
+    //   }
+    // 只需要传递 converationInfo 信息,整体返回的数据格式不可改变
+    // promises.push(Promise.resolve(converationInfo));
+    // });
+    // 只需要传递 converationInfo 信息,整体返回的数据格式不可改变
+    promises.push(...tempConversations);
+    return Promise.all(promises);
+  },
+
+  // 获取群组详情
+  getGroupMembers: async conversation => {
+    // 通过 conversation 的 targetid 获取群组成员信息
+    // groupMembers 为群组成员 list,需要构建成对象数组。
+    // 特别注意:如果传递的群组成员信息不准确会影响 @ 信息的发送和群组成员昵称的展示
+    try {
+      const res = await queryGroupMemberList({
+        imGroupId: conversation.targetId
+      });
+      const groupMembers = res.data.map(item => {
+        return {
+          id: item.userId,
+          name: item.nickname,
+          portraitUri: item.user.avatar || teacherLogo
+        };
+      });
+      console.log(groupMembers, "groupMembers");
+      return Promise.resolve(groupMembers);
+    } catch {
+      //
+      return Promise.resolve([]);
+    }
+  }
+};

+ 105 - 92
src/main.js

@@ -1,78 +1,79 @@
-import Vue from 'vue'
-import ElementUI from 'element-ui'
-import './assets/icon/iconfont.css'
-import dayjs from 'dayjs'
-import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
-import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'
-import isBetween from 'dayjs/plugin/isBetween'
-import numeral from 'numeral'
-import lodash from 'lodash'
-import qs from 'qs'
-import PortalVue from 'portal-vue'
-Vue.use(PortalVue)
-import {
-  permission
-} from "@/utils/directivePage";
-import {getTenantId} from "@/utils/auth"
-dayjs.extend(isSameOrBefore)
-dayjs.extend(isSameOrAfter)
-dayjs.extend(isBetween)
-
-import * as constant from '@/constant'
-
-import 'normalize.css/normalize.css' // A modern alternative to CSS resets
-import 'default-passive-events'
-import 'babel-polyfill'
+import Vue from "vue";
+import ElementUI from "element-ui";
+import "./assets/icon/iconfont.css";
+import dayjs from "dayjs";
+import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
+import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
+import isBetween from "dayjs/plugin/isBetween";
+import numeral from "numeral";
+import lodash from "lodash";
+import qs from "qs";
+import PortalVue from "portal-vue";
+Vue.use(PortalVue);
+import { permission } from "@/utils/directivePage";
+import { getTenantId } from "@/utils/auth";
+dayjs.extend(isSameOrBefore);
+dayjs.extend(isSameOrAfter);
+dayjs.extend(isBetween);
+
+import * as constant from "@/constant";
+
+import "normalize.css/normalize.css"; // A modern alternative to CSS resets
+// import "default-passive-events";
+import "babel-polyfill";
 // import './theme/index.css'
 
 // import './global.scss'
 
 // import 'element-ui/lib/theme-chalk/index.css'
-import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n
+import locale from "element-ui/lib/locale/lang/zh-CN"; // lang i18n
 
-import '@/styles/index.scss' // global css
-import '@/styles/iconfont/iconfont.css' // 字体图标,主要用于菜单图标
+import "@/styles/index.scss"; // global css
+import "@/styles/iconfont/iconfont.css"; // 字体图标,主要用于菜单图标
 
 // import '@/assets/element-variables.scss'
-import App from './App'
-import store from './store'
-import installComponents from '@/components/install'
-import router from './router'
-import './utils/vueFilter'
-import './utils/directive'
+import App from "./App";
+import store from "./store";
+import installComponents from "@/components/install";
+import router from "./router";
+import "./utils/vueFilter";
+import "./utils/directive";
 // Vue.use(vueFilter)
-import '@/icons' // icon
-import '@/permission' // permission control
-import {
-  Message
-} from 'element-ui'
-const showMessage = Symbol('showMessage')
+import "@/icons"; // icon
+import "@/permission"; // permission control
+import { Message } from "element-ui";
+const showMessage = Symbol("showMessage");
+
+// imkit 为核心模块
+import { defineCustomElements } from "@rongcloud/imkit";
+defineCustomElements();
+
 class DonMessage {
   success(options, single = true) {
-    this[showMessage]('success', options, single)
+    this[showMessage]("success", options, single);
   }
   warning(options, single = true) {
-    this[showMessage]('warning', options, single)
+    this[showMessage]("warning", options, single);
   }
   info(options, single = true) {
-    this[showMessage]('info', options, single)
+    this[showMessage]("info", options, single);
   }
   error(options, single = true) {
-      this[showMessage]('error', options, single)
+    this[showMessage]("error", options, single);
   }
   [showMessage](type, options, single) {
     // console.log(type, options, Message)
     let params = {
       message: options,
-      offset: 90,
-    }
+      offset: 90
+    };
     if (single) {
       // 判断是否已存在Message
-      if (document.getElementsByClassName('el-message').length === 0) {
-        Message[type](params)
+      if (document.getElementsByClassName("el-message").length === 0) {
+        Message[type](params);
       }
     } else {
-      Message[type](params)
+      Message[type](params);
     }
   }
 }
@@ -82,28 +83,33 @@ ElementUI.Dialog.props.closeOnClickModal.default = false;
 // ElementUI.Dialog.props.destroyOnClose.default = true;
 //  (ElementUI.Input)
 // 全局修改选择如果value与label都为数字0则清空
-const SelectValueWatch = ElementUI.Select.watch.value
-ElementUI.Select.watch.value = function (newValue, oldValue) {
-  SelectValueWatch.call(this, newValue, oldValue)
-  if (this.selected && this.selected.value === 0 && this.selected.currentLabel === 0 && newValue !== oldValue) {
+const SelectValueWatch = ElementUI.Select.watch.value;
+ElementUI.Select.watch.value = function(newValue, oldValue) {
+  SelectValueWatch.call(this, newValue, oldValue);
+  if (
+    this.selected &&
+    this.selected.value === 0 &&
+    this.selected.currentLabel === 0 &&
+    newValue !== oldValue
+  ) {
     this.handleClearClick.call(this, {
       stopPropagation: () => {}
-    })
+    });
   }
-}
+};
 
 // Vue.use(ElementUI)
-Vue.use(installComponents)
+Vue.use(installComponents);
 // 命名根据需要,DonMessage只是在文章中使用
-export const $message = new DonMessage()
-Vue.prototype.$message = $message
+export const $message = new DonMessage();
+Vue.prototype.$message = $message;
 
 // 全局移除数字滚动
-document.addEventListener('mousewheel', function () {
-  if (document.activeElement.type === 'number') {
-    document.activeElement.blur()
+document.addEventListener("mousewheel", function() {
+  if (document.activeElement.type === "number") {
+    document.activeElement.blur();
   }
-})
+});
 // 费用审核输入框回车之后失去焦点有问题,所以注释
 // document.addEventListener('keydown', function (event) {
 //   if (event.keyCode == 13) {
@@ -126,29 +132,28 @@ document.addEventListener('mousewheel', function () {
 // }
 
 // 高德地址
-import VueAMap from 'vue-amap'
-Vue.use(VueAMap)
+import VueAMap from "vue-amap";
+Vue.use(VueAMap);
 
 // 检测浏览器是否缩放
 // import '@/utils/zoom'
 Vue.prototype.$ELEMENT = {
-  size: 'medium',
+  size: "medium",
   zIndex: 3000
-}
+};
 // set ElementUI lang to EN
 Vue.use(ElementUI, {
   locale
-})
-
-Vue.config.productionTip = false
-Vue.prototype.$bus = new Vue()
+});
 
+Vue.config.productionTip = false;
+Vue.prototype.$bus = new Vue();
 
 // 将selects全局混入当前vue实例中
 Vue.mixin({
   computed: {
     selects() {
-      return store.state.selects
+      return store.state.selects;
     },
     $helpers() {
       return {
@@ -157,44 +162,52 @@ Vue.mixin({
         lodash,
         qs,
         permission,
-        tenantId:getTenantId()
-
-      }
+        tenantId: getTenantId()
+      };
     },
     $constant() {
-      return constant
+      return constant;
     }
   },
   methods: {
-    isNumber (val, max) { // 只能输入正整数,且可以限制最大值,用法:@input="e => (form.cloud_room_up_limit = isNumber(e, 7))"
+    isNumber(val, max) {
+      // 只能输入正整数,且可以限制最大值,用法:@input="e => (form.cloud_room_up_limit = isNumber(e, 7))"
       val = val.replace(/\b(0+)+[^0-9]*/gi, "");
-      if(val > max) {
-        val = max
+      if (val > max) {
+        val = max;
       }
       return val;
     },
-    keyupEvent(e,input){ // 正数,小数2位 @keyup.native='keyupEvent($event)'
-      e.target.value=e.target.value.replace(/[^\d.]/g, '');
-      e.target.value=e.target.value.replace(/\.{2,}/g, '.');
-      e.target.value=e.target.value.replace(/^\./g, '0.');
-      e.target.value=e.target.value.replace(/^\d*\.\d*\./g, e.target.value.substring(0,e.target.value.length-1));
-      e.target.value=e.target.value.replace(/^0[^\.]+/g, '0')
-      e.target.value=e.target.value.replace(/^(\d+)\.(\d\d).*$/, '$1.$2')
+    keyupEvent(e, input) {
+      // 正数,小数2位 @keyup.native='keyupEvent($event)'
+      e.target.value = e.target.value.replace(/[^\d.]/g, "");
+      e.target.value = e.target.value.replace(/\.{2,}/g, ".");
+      e.target.value = e.target.value.replace(/^\./g, "0.");
+      e.target.value = e.target.value.replace(
+        /^\d*\.\d*\./g,
+        e.target.value.substring(0, e.target.value.length - 1)
+      );
+      e.target.value = e.target.value.replace(/^0[^\.]+/g, "0");
+      e.target.value = e.target.value.replace(/^(\d+)\.(\d\d).*$/, "$1.$2");
     },
     changeHash(value) {
-      const origin = window.location.origin
-      history.replaceState("", "", `${origin}/#${this.$route.path}?opt=${value}`)
+      const origin = window.location.origin;
+      history.replaceState(
+        "",
+        "",
+        `${origin}/#${this.$route.path}?opt=${value}`
+      );
     },
     getFullPermission(str) {
-      let routeName = this.$route.path
-      return str + routeName
+      let routeName = this.$route.path;
+      return str + routeName;
     }
   }
-})
+});
 
 new Vue({
-  el: '#app',
+  el: "#app",
   router,
   store,
   render: h => h(App)
-})
+});

+ 186 - 153
src/store/modules/user.js

@@ -1,215 +1,248 @@
-import { login, logout, getInfo } from '@/api/user'
-import { getToken, setToken, removeToken, removeCrossToken, setCrossToken, removeTenant } from '@/utils/auth'
-import { tenantInfoQueryPage, tenantInfoInfo } from '@/views/organManager/api'
-import { resetRouter } from '@/router'
-import { setTheme } from '@/utils/setTheme'
+import { login, logout, getInfo } from "@/api/user";
+import {
+  getToken,
+  setToken,
+  removeToken,
+  removeCrossToken,
+  setCrossToken,
+  removeTenant
+} from "@/utils/auth";
+import { tenantInfoQueryPage, tenantInfoInfo } from "@/views/organManager/api";
+import { resetRouter } from "@/router";
+import { setTheme } from "@/utils/setTheme";
 // import qs from 'qs'
 const state = {
   token: getToken(),
-  name: '',
-  avatar: '',
-  positionName: '', // 职位
-  organ: '',
-  organName: '',
-  phone: '',
-  isSuperAdmin:false,
-  roles:[],
+  name: "",
+  avatar: "",
+  positionName: "", // 职位
+  organ: "",
+  organName: "",
+  phone: "",
+  isSuperAdmin: false,
+  roles: [],
   tenantId: null,
   baseTenantId: null,
-}
+  imToken: ""
+};
 
-let stateTenantId = null // 机构编号
+let stateTenantId = null; // 机构编号
 // organName
 const mutations = {
   SET_TOKEN: (state, token) => {
-    state.token = token
+    state.token = token;
   },
   SET_NAME: (state, name) => {
-    state.name = name
+    state.name = name;
   },
   SET_AVATAR: (state, avatar) => {
-    state.avatar = avatar
+    state.avatar = avatar;
   },
   SET_ORGAN: (state, organ) => {
-    state.organ = organ
+    state.organ = organ;
   },
   SET_ORGANNAME: (state, organName) => {
-    state.organName = organName
+    state.organName = organName;
   },
   SET_REFRESH_TOKEN: (state, refreshToken) => {
-    state.refreshToken = refreshToken
+    state.refreshToken = refreshToken;
   },
   SET_EXPIRES_IN: (state, expiresIn) => {
-    state.expiresIn = expiresIn
+    state.expiresIn = expiresIn;
   },
   SET_PHONE: (state, phone) => {
-    state.phone = phone
+    state.phone = phone;
   },
   SET_SUPERADMIN: (state, isSuperAdmin) => {
-    state.isSuperAdmin = isSuperAdmin
+    state.isSuperAdmin = isSuperAdmin;
   },
-  SET_ROLES:(state,roles)=>{
-    state.roles = roles
+  SET_ROLES: (state, roles) => {
+    state.roles = roles;
   },
-  SET_TENANTID:(state,tenantId)=>{
-    state.tenantId = tenantId
+  SET_TENANTID: (state, tenantId) => {
+    state.tenantId = tenantId;
   },
-  SET_POSITIONNAME:(state,positionName)=>{
-    state.positionName = positionName
+  SET_POSITIONNAME: (state, positionName) => {
+    state.positionName = positionName;
   },
-  SET_BASETENANTID:(state,baseTenantId)=>{
-    state.baseTenantId = baseTenantId
+  SET_BASETENANTID: (state, baseTenantId) => {
+    state.baseTenantId = baseTenantId;
   },
-}
+  SET_IMTOKEN: (state, imToken) => {
+    state.imToken = imToken;
+  }
+};
 
 async function tenantQueryPage(id) {
   try {
-    let tenantId = id
-    if(tenantId < 0) {
-      const res = await tenantInfoQueryPage({ page: 1, rows: 1, payState: 1, state: 1 })
-      const tenantList = res.data?.rows || []
-      tenantId = tenantList[0]?.id
+    let tenantId = id;
+    if (tenantId < 0) {
+      const res = await tenantInfoQueryPage({
+        page: 1,
+        rows: 1,
+        payState: 1,
+        state: 1
+      });
+      const tenantList = res.data?.rows || [];
+      tenantId = tenantList[0]?.id;
     }
-    stateTenantId = tenantId
-    if(tenantId) {
-      const info = await tenantInfoInfo({id: tenantId})
-      const data = info.data
-      stateTenantId = data.id
-      sessionStorage.setItem('tenantConfig', JSON.stringify({
-        themeColor: data.config.themeColor,
-        theme: data.config.theme,
-        tenantId: data.id,
-        tenantName: data.name,
-        tenantLogo: data.logo,
-        tenantStatus: 'on' // 判断是否此状态,没有的话,刷新页面会重新请求页面
-      }))
+    stateTenantId = tenantId;
+    if (tenantId) {
+      const info = await tenantInfoInfo({ id: tenantId });
+      const data = info.data;
+      stateTenantId = data.id;
+      sessionStorage.setItem(
+        "tenantConfig",
+        JSON.stringify({
+          themeColor: data.config.themeColor,
+          theme: data.config.theme,
+          tenantId: data.id,
+          tenantName: data.name,
+          tenantLogo: data.logo,
+          tenantStatus: "on" // 判断是否此状态,没有的话,刷新页面会重新请求页面
+        })
+      );
     }
-  } catch(e) {}
+  } catch (e) {}
 }
 
 const actions = {
-  setUserName({commit}, userName) {
-    commit('SET_NAME',userName)
+  setUserName({ commit }, userName) {
+    commit("SET_NAME", userName);
   },
-  setUserAvatar({commit}, userName) {
-    commit('SET_AVATAR',userName)
+  setUserAvatar({ commit }, userName) {
+    commit("SET_AVATAR", userName);
   },
   // user login
- async login ({ commit }, userInfo) {
-    const { username, password } = userInfo
+  async login({ commit }, userInfo) {
+    const { username, password } = userInfo;
     return await new Promise((resolve, reject) => {
       //qs.stringify({ username: username.trim(), password: password, clientId: 'app', clientSecret: 'app' })
       // { username: username.trim(), password: password }
-      login({ username: username.trim(), password: password, clientId: 'system', clientSecret: 'system' }).then(response => {
-        const { data } = response
-        if (response.code == 200) {
-          let token = data.authentication.token_type + ' ' + data.authentication.access_token;
-          commit('SET_REFRESH_TOKEN', data.authentication.refresh_token)
-          commit('SET_EXPIRES_IN', data.authentication.expires_in)
-          commit('SET_TOKEN', token)
-          setToken(token)
-          setCrossToken(data.authentication.access_token)
-          resolve()
-        }
-      }).catch(error => {
-        reject(error)
-        console.log('登录错误',error)
+      login({
+        username: username.trim(),
+        password: password,
+        clientId: "system",
+        clientSecret: "system"
       })
-    })
-  },
-  // get 获取用户信息
- async getInfo ({ commit, state }) {
-    return  new Promise(async (resolve, reject) => {
-      try{
-        await getInfo(state.token).then(async (response) => {
+        .then(response => {
+          const { data } = response;
           if (response.code == 200) {
-            const data = response
-            if (!data.data) {
-              reject('获取用户信息错误,请重新登录')
-            }
-            const username = data.data.realName || data.data.username;
-            const avatar = data.data.avatar;
-            const organ = data.data.organId;
-            const organName = data.data.organName;
-            const phone = data.data.phone
-            const isSuperAdmin = data.data.isSuperAdmin
-            const roles = data.data?.positions?.split(',')||[]
-            const tenantId = data.data.tenantId
-            const positionName = data.data.positionName
-            sessionStorage.setItem('baseTenantId', tenantId)
-            commit('SET_BASETENANTID', tenantId)
-            let tenantConfig = sessionStorage.getItem('tenantConfig')
-            tenantConfig = tenantConfig ? JSON.parse(tenantConfig) : {}
-            if(tenantConfig.tenantStatus != 'on') {
-              await tenantQueryPage(tenantId)
-              // 会重置数据
-              tenantConfig = sessionStorage.getItem('tenantConfig')
-              tenantConfig = tenantConfig ? JSON.parse(tenantConfig) : {}
-            }
-            if(tenantId > 0) { // 判断是机构才会根据主题去设置
-              const themeColor = tenantConfig.themeColor
-              const theme = tenantConfig.theme
-              setTheme({ theme, themeColor })
-            }
-            commit('SET_TENANTID', stateTenantId)
-            commit('SET_NAME', username)
-            commit('SET_AVATAR', avatar)
-            commit('SET_ORGAN', organ)
-            commit('SET_ORGANNAME', organName)
-            commit('SET_PHONE', phone)
-            commit('SET_SUPERADMIN',isSuperAdmin)
-            commit('SET_ROLES',roles)
-            commit('SET_POSITIONNAME',positionName)
-            resolve(data)
-          }else {
-            reject('获取用户信息错误,请重新登录')
+            let token =
+              data.authentication.token_type +
+              " " +
+              data.authentication.access_token;
+            commit("SET_REFRESH_TOKEN", data.authentication.refresh_token);
+            commit("SET_EXPIRES_IN", data.authentication.expires_in);
+            commit("SET_TOKEN", token);
+            setToken(token);
+            setCrossToken(data.authentication.access_token);
+            resolve();
           }
-        }).catch(error => {
-          reject('获取用户信息错误,请重新登录')
         })
-      }catch(e){
-        reject('获取用户信息错误,请重新登录')
+        .catch(error => {
+          reject(error);
+          console.log("登录错误", error);
+        });
+    });
+  },
+  // get 获取用户信息
+  async getInfo({ commit, state }) {
+    return new Promise(async (resolve, reject) => {
+      try {
+        await getInfo(state.token)
+          .then(async response => {
+            if (response.code == 200) {
+              const data = response;
+              if (!data.data) {
+                reject("获取用户信息错误,请重新登录");
+              }
+              const username = data.data.realName || data.data.username;
+              const avatar = data.data.avatar;
+              const organ = data.data.organId;
+              const organName = data.data.organName;
+              const phone = data.data.phone;
+              const isSuperAdmin = data.data.isSuperAdmin;
+              const roles = data.data?.positions?.split(",") || [];
+              const tenantId = data.data.tenantId;
+              const positionName = data.data.positionName;
+              const imToken = data.data.imToken;
+              sessionStorage.setItem("baseTenantId", tenantId);
+              commit("SET_BASETENANTID", tenantId);
+              let tenantConfig = sessionStorage.getItem("tenantConfig");
+              tenantConfig = tenantConfig ? JSON.parse(tenantConfig) : {};
+              if (tenantConfig.tenantStatus != "on") {
+                await tenantQueryPage(tenantId);
+                // 会重置数据
+                tenantConfig = sessionStorage.getItem("tenantConfig");
+                tenantConfig = tenantConfig ? JSON.parse(tenantConfig) : {};
+              }
+              if (tenantId > 0) {
+                // 判断是机构才会根据主题去设置
+                const themeColor = tenantConfig.themeColor;
+                const theme = tenantConfig.theme;
+                setTheme({ theme, themeColor });
+              }
+              commit("SET_TENANTID", stateTenantId);
+              commit("SET_NAME", username);
+              commit("SET_AVATAR", avatar);
+              commit("SET_ORGAN", organ);
+              commit("SET_ORGANNAME", organName);
+              commit("SET_PHONE", phone);
+              commit("SET_SUPERADMIN", isSuperAdmin);
+              commit("SET_ROLES", roles);
+              commit("SET_POSITIONNAME", positionName);
+              commit("SET_IMTOKEN", imToken);
+              resolve(data);
+            } else {
+              reject("获取用户信息错误,请重新登录");
+            }
+          })
+          .catch(error => {
+            reject("获取用户信息错误,请重新登录");
+          });
+      } catch (e) {
+        reject("获取用户信息错误,请重新登录");
       }
-      })
-
-
+    });
   },
   // 登出
-  logout ({ commit }) {
-    return  new Promise((resolve, reject) => {
-      logout().then(() => {
-        commit('SET_TOKEN', '')
-        removeToken()
-        setToken('')
-        removeCrossToken()
-        resetRouter()
-        removeTenant() // 移除机构信息
-        commit('SET_NAME', '')
-        console.log(getToken)
-        resolve()
-      }).catch(error => {
-        reject(error)
-      })
-    })
+  logout({ commit }) {
+    return new Promise((resolve, reject) => {
+      logout()
+        .then(() => {
+          commit("SET_TOKEN", "");
+          removeToken();
+          setToken("");
+          removeCrossToken();
+          resetRouter();
+          removeTenant(); // 移除机构信息
+          commit("SET_NAME", "");
+          console.log(getToken);
+          resolve();
+        })
+        .catch(error => {
+          reject(error);
+        });
+    });
   },
 
   // remove token
-  resetToken ({ commit }) {
+  resetToken({ commit }) {
     return new Promise(resolve => {
-      commit('SET_TOKEN', '')
-      removeToken()
-      removeCrossToken()
-      removeTenant() // 移除机构信息
-      resolve()
-      setToken('')
-    })
+      commit("SET_TOKEN", "");
+      removeToken();
+      removeCrossToken();
+      removeTenant(); // 移除机构信息
+      resolve();
+      setToken("");
+    });
   }
-}
+};
 
 export default {
   namespaced: true,
   state,
   mutations,
   actions
-}
-
+};

+ 103 - 95
vue.config.js

@@ -1,11 +1,11 @@
-'use strict'
-const path = require('path')
-const defaultSettings = require('./src/settings.js')
+"use strict";
+const path = require("path");
+const defaultSettings = require("./src/settings.js");
 
 function resolve(dir) {
-  return path.join(__dirname, dir)
+  return path.join(__dirname, dir);
 }
-const name = defaultSettings.title || '管乐迷后台管理系统' // page title
+const name = defaultSettings.title || "管乐迷后台管理系统"; // page title
 
 // If your port is set to 80,
 // use administrator privileges to execute the command line.
@@ -20,7 +20,7 @@ const name = defaultSettings.title || '管乐迷后台管理系统' // page titl
 // let target = 'http://192.168.3.20:8000' //邹璇
 // let target = 'http://192.168.3.119:8000' //勇哥
 // let target = 'http://dev.dayaedu.com' // 开发环境
-let target = 'https://test.dayaedu.com' //测试环境
+let target = "https://test.dayaedu.com"; //测试环境
 // All configuration item explanations can be find in https://cli.vuejs.org/config/
 module.exports = {
   /**
@@ -30,27 +30,27 @@ module.exports = {
    * In most cases please use '/' !!!
    * Detail: https://cli.vuejs.org/config/#publicpath
    */
-  publicPath: './',
-  outputDir: 'dist',
-  assetsDir: 'static',
+  publicPath: "./",
+  outputDir: "dist",
+  assetsDir: "static",
   lintOnSave: false,
   productionSourceMap: false,
 
   // 以下是pwa配置
   pwa: {
     iconPaths: {
-      favicon32: 'favicon1.ico',
-      favicon16: 'favicon1.ico',
-      appleTouchIcon: 'favicon1.ico',
-      maskIcon: 'favicon1.ico',
-      msTileImage: 'favicon1.ico'
+      favicon32: "favicon1.ico",
+      favicon16: "favicon1.ico",
+      appleTouchIcon: "favicon1.ico",
+      maskIcon: "favicon1.ico",
+      msTileImage: "favicon1.ico"
     }
   },
   devServer: {
     disableHostCheck: true,
     open: false,
     hot: true,
-    port:3005,
+    port: 3005,
     // overlay: {
     //   warnings: false,
     //   errors: true
@@ -65,60 +65,59 @@ module.exports = {
       // http://47.114.176.40:8000
       // let target = 'http://dev.dayaedu.com'
       // 'http://dev.dayaedu.com'
-      '/api-auth': {
+      "/api-auth": {
         target: target,
         // target : target,
         changeOrigin: true,
         pathRewrite: {
-          '^api-auth': ''
+          "^api-auth": ""
         }
       },
-      '/api-task': {
+      "/api-task": {
         target: target,
         changeOrigin: true,
         pathRewrite: {
-          '^api-task': ''
+          "^api-task": ""
         }
       },
-      '/api-web': {
+      "/api-web": {
         target: target,
         changeOrigin: true,
         pathRewrite: {
-          '^api-web': ''
+          "^api-web": ""
         }
       },
-      '/api-cms': {
+      "/api-cms": {
         target: target,
         changeOrigin: true,
         pathRewrite: {
-          '^api-cms': ''
+          "^api-cms": ""
         }
       },
-      '/api-teacher': {
+      "/api-teacher": {
         target: target,
         changeOrigin: true,
         pathRewrite: {
-          '^api-teacher': ''
+          "^api-teacher": ""
         }
       },
-      '/api-oa': {
+      "/api-oa": {
         target: target,
         changeOrigin: true,
         pathRewrite: {
-          '^api-oa': ''
+          "^api-oa": ""
         }
       },
-      '/jiari': {
-        target: 'http://tool.bitefu.net',
-        changeOrigin: true,
+      "/jiari": {
+        target: "http://tool.bitefu.net",
+        changeOrigin: true
       },
-      '/instructions': {
+      "/instructions": {
         target: defaultSettings.instructions,
-        changeOrigin: true,
-      },
+        changeOrigin: true
+      }
       // instructions
-
-    },
+    }
   },
   configureWebpack: {
     // provide the app's title in webpack's name field, so that
@@ -126,87 +125,96 @@ module.exports = {
     name: name,
     resolve: {
       alias: {
-        '@': resolve('src'),
-        '@scss':path.resolve(__dirname,'src')
+        "@": resolve("src"),
+        "@scss": path.resolve(__dirname, "src")
       }
     }
   },
   chainWebpack(config) {
-    config.plugins.delete('preload') // TODO: need test
-    config.plugins.delete('prefetch') // TODO: need test
+    config.plugins.delete("preload"); // TODO: need test
+    config.plugins.delete("prefetch"); // TODO: need test
     config.resolve.symlinks(true);
 
     // set svg-sprite-loader
     config.module
-      .rule('svg')
-      .exclude.add(resolve('src/icons'))
-      .end()
+      .rule("svg")
+      .exclude.add(resolve("src/icons"))
+      .end();
     config.module
-      .rule('icons')
+      .rule("icons")
       .test(/\.svg$/)
-      .include.add(resolve('src/icons'))
+      .include.add(resolve("src/icons"))
       .end()
-      .use('svg-sprite-loader')
-      .loader('svg-sprite-loader')
+      .use("svg-sprite-loader")
+      .loader("svg-sprite-loader")
       .options({
-        symbolId: 'icon-[name]'
+        symbolId: "icon-[name]"
       })
-      .end()
+      .end();
 
     // set preserveWhitespace
     config.module
-      .rule('vue')
-      .use('vue-loader')
-      .loader('vue-loader')
+      .rule("vue")
+      .use("vue-loader")
+      .loader("vue-loader")
       .tap(options => {
-        options.compilerOptions.preserveWhitespace = true
-        return options
+        // Vue 项目需要添加过滤自定义组件配置。
+        options.compilerOptions = {
+          ...options.compilerOptions,
+          isCustomElement: tag => {
+            return (
+              ["conversation-list", "message-list", "message-editor"].indexOf(
+                tag
+              ) !== -1
+            );
+          }
+        };
+        options.compilerOptions.preserveWhitespace = true;
+        return options;
       })
-      .end()
+      .end();
 
     config
       // https://webpack.js.org/configuration/devtool/#development
-      .when(process.env.NODE_ENV === 'development',
-        config => config.devtool('cheap-source-map')
-      )
+      .when(process.env.NODE_ENV === "development", config =>
+        config.devtool("cheap-source-map")
+      );
 
-    config
-      .when(process.env.NODE_ENV !== 'development',
-        config => {
-          config
-            .plugin('ScriptExtHtmlWebpackPlugin')
-            .after('html')
-            .use('script-ext-html-webpack-plugin', [{
-              // `runtime` must same as runtimeChunk name. default is `runtime`
-              inline: /runtime\..*\.js$/
-            }])
-            .end()
-          config
-            .optimization.splitChunks({
-              chunks: 'all',
-              cacheGroups: {
-                libs: {
-                  name: 'chunk-libs',
-                  test: /[\\/]node_modules[\\/]/,
-                  priority: 10,
-                  chunks: 'initial' // only package third parties that are initially dependent
-                },
-                elementUI: {
-                  name: 'chunk-elementUI', // split elementUI into a single package
-                  priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
-                  test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
-                },
-                commons: {
-                  name: 'chunk-commons',
-                  test: resolve('src/components'), // can customize your rules
-                  minChunks: 3, //  minimum common number
-                  priority: 5,
-                  reuseExistingChunk: true
-                }
-              }
-            })
-          config.optimization.runtimeChunk('single')
+    config.when(process.env.NODE_ENV !== "development", config => {
+      config
+        .plugin("ScriptExtHtmlWebpackPlugin")
+        .after("html")
+        .use("script-ext-html-webpack-plugin", [
+          {
+            // `runtime` must same as runtimeChunk name. default is `runtime`
+            inline: /runtime\..*\.js$/
+          }
+        ])
+        .end();
+      config.optimization.splitChunks({
+        chunks: "all",
+        cacheGroups: {
+          libs: {
+            name: "chunk-libs",
+            test: /[\\/]node_modules[\\/]/,
+            priority: 10,
+            chunks: "initial" // only package third parties that are initially dependent
+          },
+          elementUI: {
+            name: "chunk-elementUI", // split elementUI into a single package
+            priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
+            test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
+          },
+          commons: {
+            name: "chunk-commons",
+            test: resolve("src/components"), // can customize your rules
+            minChunks: 3, //  minimum common number
+            priority: 5,
+            reuseExistingChunk: true
+          }
         }
-      )
+      });
+      config.optimization.runtimeChunk("single");
+    });
   }
-}
+};

Some files were not shown because too many files changed in this diff