wansfa 1 year ago
parent
commit
b755b5b933

+ 19 - 0
src/api/qw/im.js

@@ -43,6 +43,15 @@ export function getMessageList(query) {
 }
 
 
+export function getHistoryList(query) {
+  return request({
+    url: '/qw/message/getHistoryList',
+    method: 'get',
+    params: query
+  })
+}
+
+
 // 发送文本消息
 export function sendTextMsg(data) {
   return request({
@@ -73,5 +82,15 @@ export function getRoomInfo(roomId) {
 
 
 
+//导出消息
+export function exportMessage(query) {
+  return request({
+    url: '/qw/message/export/',
+    method: 'get',
+    params: query
+  })
+}
+
+
 
 

+ 1389 - 0
src/components/LemonUI/components/chatmsg.vue

@@ -0,0 +1,1389 @@
+<script>
+import {
+  useScopedSlot,
+  funCall,
+  generateUUID,
+  clearHtmlExcludeImg,
+} from "../utils";
+import { isFunction, isString, isEmpty } from "../utils/validate";
+import contextmenu from "../directives/contextmenu";
+import {
+  DEFAULT_MENUS,
+  DEFAULT_MENU_LASTMESSAGES,
+  DEFAULT_MENU_CONTACTS,
+} from "../utils/constant";
+import lastContentRender from "../lastContentRender";
+
+import MemoryCache from "../utils/cache/memory";
+
+let allMessages = {};
+const emojiMap = {};
+const toPx = val => {
+  return isString(val) ? val : `${val}px`;
+};
+const toPoint = str => {
+  return str.replace("%", "") / 100;
+};
+
+let renderDrawerContent = () => {};
+
+export default {
+  name: "LemonChatui",
+  provide() {
+    return {
+      IMUI: this,
+    };
+  },
+  props: {
+    width: {
+      type: [String, Number],
+      default: 850,
+    },
+    height: {
+      type: [String, Number],
+      default: 580,
+    },
+    theme: {
+      type: String,
+      default: "default",
+    },
+    simple: {
+      type: Boolean,
+      default: false,
+    },
+    loadingText: [String, Function],
+    loadendText: [String, Function],
+    /**
+     * 消息时间格式化规则
+     */
+    messageTimeFormat: Function,
+    /**
+     * 联系人最新消息时间格式化规则
+     */
+    contactTimeFormat: Function,
+    /**
+     * 初始化时是否隐藏抽屉
+     */
+    hideDrawer: {
+      type: Boolean,
+      default: true,
+    },
+    /**
+     * 是否隐藏导航按钮上的头像
+     */
+    hideMenuAvatar: Boolean,
+    hideMenu: Boolean,
+    /**
+     * 是否隐藏消息列表内的联系人名字
+     */
+    hideMessageName: Boolean,
+    /**
+     * 是否隐藏消息列表内的发送时间
+     */
+    hideMessageTime: Boolean,
+    sendKey: Function,
+    wrapKey: Function,
+    sendText: String,
+    contextmenu: Array,
+    contactContextmenu: Array,
+    avatarCricle: Boolean,
+    user: {
+      type: Object,
+      default: () => {
+        return {};
+      },
+    },
+  },
+  data() {
+    this.CacheContactContainer = new MemoryCache();
+    this.CacheMenuContainer = new MemoryCache();
+    this.CacheMessageLoaded = new MemoryCache();
+    this.CacheDraft = new MemoryCache();
+    return {
+      drawerVisible: !this.hideDrawer,
+      currentContactId: null,
+      currentNewContactId: null,
+      currentMessages: [],
+      activeSidebar: DEFAULT_MENU_LASTMESSAGES,
+      contacts: [],
+      conversations:[],
+      menus: [],
+      editorTools: [
+          { name: "msgRecords" },
+          { name: "export" }
+        // { name: "emoji" },
+        // { name: "uploadFile" },
+        // { name: "uploadImage" },
+      ],
+    };
+  },
+
+  render() {
+    return this._renderWrapper([
+      this._renderMenu(),
+      this._renderSidebarMessage(),//加载会话数据
+      this._renderSidebarContact(),//加载联系人
+      this._renderContainer(),
+      this._renderDrawer(),
+    ]);
+  },
+  created() {
+    this.initMenus();
+  },
+  async mounted() {
+    await this.$nextTick();
+  },
+  computed: {
+    currentContact() {
+       return this.conversations.find(item =>  item.conversationId == this.currentContactId) || {};
+    },
+    //获取当前联系人
+    currentNewContact() {
+       return this.contacts.find(item =>  item.id == this.currentNewContactId) || {};
+    },
+    currentMenu() {
+      return this.menus.find(item => item.name == this.activeSidebar) || {};
+    },
+    currentIsDefSidebar() {
+      return DEFAULT_MENUS.includes(this.activeSidebar);
+    },
+    lastMessages() {
+      // var data = this.contacts.filter(item => !isEmpty(item.lastContent));
+      // data.sort((a1, a2) => {
+      //   return a2.lastSendTime - a1.lastSendTime;
+      // });
+      var data=null;
+      if(!!this.conversations){
+          data=this.conversations;
+      }
+      return data;
+    },
+  },
+  watch: {
+    activeSidebar() {},
+  },
+  methods: {
+    _menuIsContacts() {
+      return this.activeSidebar == DEFAULT_MENU_CONTACTS;
+    },
+    _menuIsMessages() {
+      return this.activeSidebar == DEFAULT_MENU_LASTMESSAGES;
+    },
+    _createMessage(message) {
+      return {
+        ...{
+          id: generateUUID(),
+          type: "text",
+          status: "going",
+          sendTime: new Date().getTime(),
+          toContactId: this.currentContactId,
+          fromUser: {
+            ...this.user,
+          },
+        },
+        ...message,
+      };
+    },
+    /**
+     * 新增一条消息
+     */
+    appendMessage(message, scrollToBottom = false) {
+        let messageList = allMessages[message.toContactId];
+        // 如果是自己的消息需要push,发送的消息不再增加未读条数
+        let conversation=this.findConversation(message.toContactId);
+        let unread=conversation.unread!=undefined && conversation.unread>0?(conversation.unread+1):1;
+        if (message.type == 'event'){
+            // if(this.user.id == message.fromUser.id){}
+            unread = 0;
+        } 
+        if (messageList === undefined) {
+          //console.log("qxj appendMessage messageList:"+messageList+",message"+JSON.stringify(message));
+          conversation.id=message.toContactId;
+          conversation.unread = unread;
+          conversation.lastSendTime=message.sendTime;
+          conversation.lastContent=this.lastContentRender(message);
+          this.updateContact(conversation);
+      } else {
+          // 如果消息存在则不再添加
+          let hasMsg = messageList.some(({id})=>id == message.id);
+          if (hasMsg) return;
+          this._addMessage(message, message.toContactId, 1);
+          
+          conversation.lastContent=this.lastContentRender(message);
+          conversation.lastSendTime=message.sendTime;
+          if (message.toContactId == this.currentContactId) {
+            conversation.unread=0;
+            if (scrollToBottom == true) {
+              this.messageViewToBottom();
+            }
+            this.CacheDraft.remove(message.toContactId);
+          } else {
+            conversation.unread = unread;
+          }
+          this.updateContact(conversation);
+      }
+    },
+    _emitSend(message, next, file) {
+      this.$emit(
+        "send",
+        message,
+        (replaceMessage = { status: "succeed" }) => {
+          next();
+          this.updateMessage(Object.assign(message, replaceMessage));
+        },
+        file,
+      );
+    },
+    _handleSend(text) {
+      const message = this._createMessage({ content: text });
+      this.appendMessage(message, true);
+      this._emitSend(message, () => {
+        this.updateContact({
+          id: message.toContactId,
+          lastContent: this.lastContentRender(message),
+          lastSendTime: message.sendTime,
+        });
+        this.CacheDraft.remove(message.toContactId);
+      });
+    },
+    _handleUpload(file) {
+      const imageTypes = ["image/gif", "image/jpeg", "image/png"];
+      let joinMessage;
+      if (imageTypes.includes(file.type)) {
+        joinMessage = {
+          type: "image",
+          content: URL.createObjectURL(file),
+        };
+      } else {
+        joinMessage = {
+          type: "file",
+          fileSize: file.size,
+          fileName: file.name,
+          content: "",
+        };
+      }
+      const message = this._createMessage(joinMessage);
+      this.appendMessage(message, true);
+      this._emitSend(
+        message,
+        () => {
+          this.updateContact({
+            id: message.toContactId,
+            lastContent: this.lastContentRender(message),
+            lastSendTime: message.sendTime,
+          });
+        },
+        file,
+      );
+    },
+    _handleRemoteImage(url) {
+        let joinMessage = {
+            type: "image",
+            content: url,
+          };
+        const message = this._createMessage(joinMessage);
+        this.appendMessage(message, true);
+        this._emitSend(
+          message,
+          () => {
+            this.updateContact({
+              id: message.toContactId,
+              lastContent: this.lastContentRender(message),
+              lastSendTime: message.sendTime,
+            });
+          },
+          null,
+        );
+    },
+    _pickImg(){
+        this.$emit("pick-image");
+    },
+    _pickMsgRecords(){
+        this.$emit("pick-msg-records");
+    },
+    _pickExportMsg(){
+        this.$emit("pick-export-msg");
+    },
+    _emitPullMessages(next) {
+      //console.log("qxj currentContact:"+JSON.stringify(this.currentContact));
+      this._changeContactLock = true;
+      this.$emit("pull-messages",
+      this.currentContact,
+        (messages = [], isEnd = false) => {
+          this._addMessage(messages, this.currentContactId, 0);
+          this.CacheMessageLoaded.set(this.currentContactId, isEnd);
+          if (isEnd == true) this.$refs.messages.loaded();
+          this.updateCurrentMessages();
+          this._changeContactLock = false;
+          next(isEnd);
+        },
+        this,
+      );
+    },
+    clearCacheContainer(name) {
+      this.CacheContactContainer.remove(name);
+      this.CacheMenuContainer.remove(name);
+    },
+    _renderWrapper(children) {
+      return (
+        <div
+          style={{
+            width: toPx(this.width),
+            height: toPx(this.height),
+          }}
+          ref="wrapper"
+          class={[
+            "lemon-wrapper",
+            `lemon-wrapper--theme-${this.theme}`,
+            { "lemon-wrapper--simple": this.simple },
+            this.drawerVisible && "lemon-wrapper--drawer-show",
+          ]}
+        >
+          {children}
+        </div>
+      );
+    },
+    _renderMenu() {
+      const menuItem = this._renderMenuItem();
+      return (
+        <div class="lemon-menu" v-show={!this.hideMenu}>
+          {
+            <lemon-avatar
+              v-show={!this.hideMenuAvatar}
+              on-click={e => {
+                this.$emit("menu-avatar-click", e);
+              }}
+              class="lemon-menu__avatar"
+              src={this.user.avatar}
+            />
+          }
+          {menuItem.top}
+          {this.$slots.menu}
+          <div class="lemon-menu__bottom">
+            {this.$slots["menu-bottom"]}
+            {menuItem.bottom}
+          </div>
+        </div>
+      );
+    },
+    _renderMenuAvatar() {
+      return;
+    },
+    _renderMenuItem() {
+      const top = [];
+      const bottom = [];
+      this.menus.forEach(item => {
+        const { name, title, unread, render, click } = item;
+        const node = (
+          <div
+            class={[
+              "lemon-menu__item",
+              { "lemon-menu__item--active": this.activeSidebar == name },
+            ]}
+            on-click={() => {
+              funCall(click, () => {
+                if (name) this.changeMenu(name);
+              });
+            }}
+            title={title}
+          >
+            <lemon-badge count={unread}>{render(item)}</lemon-badge>
+          </div>
+        );
+        item.isBottom === true ? bottom.push(node) : top.push(node);
+      });
+      return {
+        top,
+        bottom,
+      };
+    },
+    _renderSidebarMessage() {
+      return this._renderSidebar(
+        [
+          useScopedSlot(this.$scopedSlots["sidebar-message-top"], null, this),
+          this.lastMessages.map(contact => {
+            return this._renderContact(
+              {
+                contact,
+                timeFormat: this.contactTimeFormat,
+              },
+              () => this.changeContact(contact.conversationId),
+              this.$scopedSlots["sidebar-message"],
+            );
+          }),
+        ],
+        DEFAULT_MENU_LASTMESSAGES,
+        useScopedSlot(
+          this.$scopedSlots["sidebar-message-fixedtop"],
+          null,
+          this,
+        ),
+      );
+    },
+    _renderContact(props, onClick, slot) {
+      const {
+        click: customClick,
+        renderContainer,
+        id: contactId,
+      } = props.contact;
+      const click = () => {
+        funCall(customClick, () => {
+          onClick();
+          this._customContainerReady(
+            renderContainer,
+            this.CacheContactContainer,
+            contactId,
+          );
+        });
+      };
+
+      return (
+        <lemon-contact
+          class={{
+            "lemon-contact--active":this._menuIsMessages()?this.currentContactId == props.contact.conversationId:(this._menuIsContacts()?this.currentNewContactId == props.contact.id:false)
+          }}
+          v-lemon-contextmenu_contact={this.contactContextmenu}
+          props={props}
+          on-click={click}
+          scopedSlots={{ default: slot }}
+        />
+      );
+    },
+    _renderSidebarContact() {
+      let prevIndex;
+      return this._renderSidebar(
+        [
+          useScopedSlot(this.$scopedSlots["sidebar-contact-top"], null, this),
+          this.contacts.map(contact => {
+            if (!contact.index) return;
+            contact.index = contact.index.replace(/\[[0-9]*\]/, "");
+            const node = [
+              contact.index !== prevIndex && (
+                <p class="lemon-sidebar__label">{contact.index}</p>
+              ),
+              this._renderContact(
+                {
+                  contact: contact,
+                  simple: true,
+                },
+                () => {
+                  console.log("qxj contact click");
+                  this.currentNewContactId=contact.id;
+                  //this.changeContact(contact.id);
+                },
+                this.$scopedSlots["sidebar-contact"],
+              ),
+            ];
+            prevIndex = contact.index;
+            return node;
+          }),
+        ],
+        DEFAULT_MENU_CONTACTS,
+        useScopedSlot(
+          this.$scopedSlots["sidebar-contact-fixedtop"],
+          null,
+          this,
+        ),
+      );
+    },
+    _renderSidebar(children, name, fixedtop) {
+      return (
+        <div
+          class="lemon-sidebar"
+          v-show={this.activeSidebar == name}
+          on-scroll={this._handleSidebarScroll}
+        >
+          <div class="lemon-sidebar__fixed-top">{fixedtop}</div>
+          <div class="lemon-sidebar__scroll">{children}</div>
+        </div>
+      );
+    },
+    _renderDrawer() {
+      return this._menuIsMessages() && this.currentContactId ? (
+        <div class="lemon-drawer" ref="drawer">
+          {renderDrawerContent(this.currentContact)}
+          {useScopedSlot(this.$scopedSlots.drawer, "", this.currentContact)}
+        </div>
+      ) : (
+        ""
+      );
+    },
+    _isContactContainerCache(name) {
+      return name.startsWith("contact#");
+    },
+    _renderContainer() {
+      const nodes = [];
+      const cls = "lemon-container";
+      const curact = this.currentContact;
+      const curactNew = this.currentNewContact;
+      let defIsShow = true;
+      for (const name in this.CacheContactContainer.get()) {
+        const show = curactNew.id == name && this.currentIsDefSidebar;
+        if(show)defIsShow = !show;
+        nodes.push(
+          <div class={cls} v-show={show}>
+            {this.CacheContactContainer.get(name)}
+          </div>,
+        );
+      }
+      for (const name in this.CacheMenuContainer.get()) {
+          nodes.push(
+            <div
+              class={cls}
+              v-show={this.activeSidebar == name && !this.currentIsDefSidebar}
+            >
+              {this.CacheMenuContainer.get(name)}
+            </div>,
+          );
+      }
+      
+      //聊天消息View
+      nodes.push(
+        <div
+          class={cls}
+          v-show={this._menuIsMessages() && defIsShow && curact.id}
+        >
+          <div class="lemon-container__title">
+            {useScopedSlot(
+              this.$scopedSlots["message-title"],
+              <div class="lemon-container__displayname">
+                {curact.displayName}
+              </div>,
+              curact,
+            )}
+          </div>
+          <div class="lemon-vessel">
+            <div class="lemon-vessel__left">
+              <lemon-messages
+                ref="messages"
+                loading-text={this.loadingText}
+                loadend-text={this.loadendText}
+                hide-time={this.hideMessageTime}
+                hide-name={this.hideMessageName}
+                time-format={this.messageTimeFormat}
+                reverse-user-id={this.user.id}
+                on-reach-top={this._emitPullMessages}
+                messages={this.currentMessages}
+              />
+              <lemon-editor
+                ref="editor"
+                tools={this.editorTools}
+                sendText={this.sendText}
+                sendKey={this.sendKey}
+                wrapKey={this.wrapKey}
+                onSend={this._handleSend}
+                onUpload={this._handleUpload}
+                onPickImg={this._pickImg}
+                onPickMsgRecords={this._pickMsgRecords}
+                onExportMsg={this._pickExportMsg}
+              />
+            </div>
+            <div class="lemon-vessel__right">
+              {useScopedSlot(this.$scopedSlots["message-side"], null, curact)}
+            </div>
+          </div>
+        </div>,
+      );
+      nodes.push(
+        <div class={cls} v-show={!curact.id && this.currentIsDefSidebar}>
+          {this.$slots.cover}
+        </div>,
+      );
+
+      //联系人View
+      nodes.push(
+        <div
+          class={cls}
+          v-show={this._menuIsContacts() && defIsShow && curactNew.id}>
+          {useScopedSlot(
+            this.$scopedSlots["contact-info"],
+            <div class="lemon-contact-info">
+              <lemon-avatar src={curactNew.avatar} size={90} />
+              <h4>{curactNew.displayName}</h4>
+              <lemon-button
+                on-click={() => {
+                  if (isEmpty(curactNew.lastContent)) {
+                    this.updateContact({
+                      id: curactNew.id,
+                      lastContent: " ",
+                    });
+                  }
+                  this.changeContact(curactNew.conversationId, DEFAULT_MENU_LASTMESSAGES);
+                }}
+              >
+                发送消息
+              </lemon-button>
+            </div>,
+            curactNew,
+          )}
+        </div>,
+      );
+      return nodes;
+    },
+    _handleSidebarScroll() {
+      contextmenu.hide();
+    },
+    _addContact(data, t) {
+      const type = {
+        0: "unshift",
+        1: "push",
+      }[t];
+      this.contacts[type](data);
+    },
+    _addMessage(data, contactId, t) {
+      const type = {
+        0: "unshift",
+        1: "push",
+      }[t];
+      if (!Array.isArray(data)) data = [data];
+      allMessages[contactId] = allMessages[contactId] || [];
+      allMessages[contactId][type](...data);
+      console.log("qxj _addMessage type:"+type);
+    },
+    /**
+     * 设置最新消息DOM
+     * @param {String} messageType 消息类型
+     * @param {Function} render 返回消息 vnode
+     */
+    setLastContentRender(messageType, render) {
+      lastContentRender[messageType] = render;
+    },
+    lastContentRender(message) {
+      if (!isFunction(lastContentRender[message.type])) {
+        console.error(
+          `not found '${
+            message.type
+          }' of the latest message renderer,try to use ‘setLastContentRender()’`,
+        );
+        return "";
+      }
+      return lastContentRender[message.type].call(this, message);
+    },
+    /**
+     * 将字符串内的 EmojiItem.name 替换为 img
+     * @param {String} str 被替换的字符串
+     * @return {String} 替换后的字符串
+     */
+    emojiNameToImage(str) {
+      return str.replace(/\[!(\w+)\]/gi, (str, match) => {
+        const file = match;
+        return emojiMap[file]
+          ? `<img emoji-name="${match}" src="${emojiMap[file]}" />`
+          : `[!${match}]`;
+      });
+    },
+    emojiImageToName(str) {
+      return str.replace(/<img emoji-name=\"([^\"]*?)\" [^>]*>/gi, "[!$1]");
+    },
+    updateCurrentMessages() {
+      if (!allMessages[this.currentContactId]){
+          allMessages[this.currentContactId] = [];
+      }
+      //console.log("qxj allMessages:"+JSON.stringify(allMessages));
+      this.currentMessages = allMessages[this.currentContactId];
+    },
+    /**
+     * 将当前聊天窗口滚动到底部
+     */
+    messageViewToBottom() {
+      this.$refs.messages.scrollToBottom();
+    },
+    /**
+     * 设置联系人的草稿信息
+     */
+    setDraft(cid, editorValue) {
+      if (isEmpty(cid) || isEmpty(editorValue)) return false;
+      const conversation = this.findConversation(cid);
+      let lastContent = conversation.lastContent;
+      if (isEmpty(conversation)) return false;
+      if (this.CacheDraft.has(cid)) {
+        lastContent = this.CacheDraft.get(cid).lastContent;
+      }
+      this.CacheDraft.set(cid, {
+        editorValue,
+        lastContent,
+      });
+      
+      var tempDraft=this.CacheDraft.get(cid);
+      //console.log("qxj editorValue:"+editorValue+",tempDraft:"+JSON.stringify(tempDraft)+"lastContent:"+lastContent);
+
+      this.updateContact({
+        id: cid,
+        lastContent: `<span style="color:red;">[草稿]</span><span>${this.lastContentRender(
+          { type: "text", content: editorValue },
+        )}</span>`,
+      });
+    },
+    /**
+     * 清空联系人草稿信息
+     */
+    clearDraft(contactId) {
+      const draft = this.CacheDraft.get(contactId);
+      if (draft) {
+        const currentContent = this.findConversation(contactId).lastContent;
+        console.log("qxj currentContent:"+currentContent+",lastContent:"+JSON.stringify(draft));
+        //if (currentContent.indexOf('<span style="color:red;">[草稿]</span>') === 0) {
+          this.updateContact({
+            id: contactId,
+            lastContent: draft.lastContent
+          });
+        //}
+        this.CacheDraft.remove(contactId);
+      }
+    },
+    /**
+     * 改变聊天对象
+     * @param contactId 联系人 id
+     */
+    async changeContact(contactId, menuName) {
+      if (menuName) {
+           this.changeMenu(menuName);
+      } else {
+        if (this._changeContactLock || this.currentContactId == contactId){
+           return false;
+        }
+      }
+      //保存上个聊天目标的草稿
+      if (this.currentContactId) {
+        const editorValue = clearHtmlExcludeImg(this.getEditorValue()).trim();
+        if (editorValue) {
+          this.setDraft(this.currentContactId, editorValue);
+          this.setEditorValue();
+        } else {
+          this.clearDraft(this.currentContactId);
+        }
+      }
+
+      this.currentContactId = contactId;
+      if (!this.currentContactId) return false;
+      this.$emit("change-conversation", this.currentContact, this);
+      if (isFunction(this.currentContact.renderContainer) || this.activeSidebar == DEFAULT_MENU_CONTACTS) {
+          return;
+      }
+      //填充草稿内容
+      const draft = this.CacheDraft.get(contactId);
+      if (draft) this.setEditorValue(draft.editorValue);
+
+      if (this.CacheMessageLoaded.has(contactId)) {
+        this.$refs.messages.loaded();
+      } else {
+        this.$refs.messages.resetLoadState();
+      }
+
+      if (!allMessages[contactId]) {
+          this.updateCurrentMessages();
+          this._emitPullMessages(isEnd => {
+              this.messageViewToBottom();
+          });
+      } else {
+          setTimeout(() => {
+              this.updateCurrentMessages();
+              this.messageViewToBottom();
+          }, 0);
+      }
+    },
+    /**
+     * 删除一条聊天消息
+     * @param messageId 消息 id
+     * @param contactId 联系人 id
+     */
+    removeMessage(messageId) {
+      const message = this.findMessage(messageId);
+      if (!message) return false;
+      const index = allMessages[message.toContactId].findIndex(
+        ({ id }) => id == messageId,
+      );
+      allMessages[message.toContactId].splice(index, 1);
+      return true;
+    },
+    /**
+     * 修改聊天一条聊天消息
+     * @param {Message} data 根据 data.id 查找聊天消息并覆盖传入的值
+     * @param contactId 联系人 id
+     */
+    updateMessage(message) {
+      if (!message.id) return false;
+      let historyMessage = this.findMessage(message.id);
+      if (!historyMessage) return false;
+      historyMessage = Object.assign(historyMessage, message, {
+        toContactId: historyMessage.toContactId,
+      });
+      return true;
+    },
+    /**
+     * 手动更新对话消息
+     * @param {String} messageId 消息ID,如果为空则更新当前聊天窗口的所有消息
+     */
+    forceUpdateMessage(messageId) {
+      if (!messageId) {
+        this.$refs.messages.$forceUpdate();
+      } else {
+        const components = this.$refs.messages.$refs.message;
+        if (components) {
+          const messageComponent = components.find(
+            com => com.$attrs.message.id == messageId,
+          );
+          if (messageComponent) messageComponent.$forceUpdate();
+        }
+      }
+    },
+    _customContainerReady(render, cacheDrive, key) {
+      if (isFunction(render) && !cacheDrive.has(key)) {
+        cacheDrive.set(key, render.call(this));
+      }
+    },
+    /**
+     * 切换左侧按钮
+     * @param {String} name 按钮 name
+     */
+    changeMenu(name) {
+      this.$emit("change-menu", name);
+      this.activeSidebar = name;
+    },
+    /**
+     * 初始化编辑框的 Emoji 表情列表,是 Lemon-editor.initEmoji 的代理方法
+     * @param {Array<Emoji,EmojiItem>} data emoji 数据
+     * Emoji = {label: 表情,children: [{name: wx,title: 微笑,src: url}]} 分组
+     * EmojiItem = {name: wx,title: 微笑,src: url} 无分组
+     */
+    initEmoji(data) {
+      let flatData = [];
+      this.$refs.editor.initEmoji(data);
+      if (data[0].label) {
+        data.forEach(item => {
+          flatData.push(...item.children);
+        });
+      } else {
+        flatData = data;
+      }
+      flatData.forEach(({ name, src }) => (emojiMap[name] = src));
+    },
+    initEditorTools(data) {
+      //this.editorTools = data;
+      this.editorTools = data;
+      //this.$refs.editor.initTools(data);
+    },
+    /**
+     * 初始化左侧按钮
+     * @param {Array<Menu>} data 按钮数据
+     */
+    initMenus(data) {
+      const defaultMenus = [
+        {
+          name: DEFAULT_MENU_LASTMESSAGES,
+          title: "聊天",
+          unread: 0,
+          click: null,
+          render: menu => {
+            return <i class="lemon-icon-message" />;
+          },
+          isBottom: false,
+        },
+        {
+          name: DEFAULT_MENU_CONTACTS,
+          title: "通讯录",
+          unread: 0,
+          click: null,
+          render: menu => {
+            return <i class="lemon-icon-addressbook" />;
+          },
+          isBottom: false,
+        },
+      ];
+      let menus = [];
+      if (Array.isArray(data)) {
+        const indexMap = {
+          messages: 0,
+          contacts: 1,
+        };
+        const indexKeys = Object.keys(indexMap);
+        menus = data.map(item => {
+          if (indexKeys.includes(item.name)) {
+            return {
+              ...defaultMenus[indexMap[item.name]],
+              ...item,
+              ...{ renderContainer: null },
+            };
+          }
+          if (item.renderContainer) {
+            this._customContainerReady(
+              item.renderContainer,
+              this.CacheMenuContainer,
+              item.name,
+            );
+          }
+          return item;
+        });
+      } else {
+        menus = defaultMenus;
+      }
+      this.menus = menus;
+    },
+    /**
+     * 初始化联系人数据
+     * @param {Array<Contact>} data 联系人列表
+     */
+    initContacts(data) {
+      this.contacts = data;
+      this.sortContacts();
+      if(!!this.contacts>0){
+          this.currentNewContactId=(this.contacts[0]).id;
+      }
+    },
+      /**
+     * 初始化会话数据
+     * @param {Array<Contact>} data 会话列表
+     */
+    initConversations(data) {
+      this.conversations = data;
+      this.sortConversations();
+      
+    },
+
+    /**
+     * 使用 联系人的 index 值进行排序
+     */
+    sortContacts() {
+      this.contacts.sort((a, b) => {
+        if (!a.index) return;
+        return a.index.localeCompare(b.index);
+      });
+    },
+    sortConversations() {
+      this.conversations.sort((a, b) => {
+        if (!a.index) return;
+        return a.index.localeCompare(b.index);
+      });
+    },
+    appendContact(contact) {
+      if (isEmpty(contact.id) || isEmpty(contact.displayName)) {
+        console.error("id | displayName cant be empty");
+        return false;
+      }
+      if (this.hasContact(contact.id)) return true;
+      this.contacts.push(
+        Object.assign(
+          {
+            id: "",
+            displayName: "",
+            avatar: "",
+            index: "",
+            unread: 0,
+            lastSendTime: "",
+            lastContent: "",
+          },
+          contact,
+        ),
+      );
+      return true;
+    },
+
+     appendConversation(conversation) {
+      if (isEmpty(conversation.conversationId) || isEmpty(conversation.displayName)) {
+        console.error("id | displayName cant be empty");
+        return false;
+      }
+      if (this.hasConversation(conversation.conversationId)) return true;
+      let insConversation=Object.assign({
+            conversationId: "",
+            displayName: "",
+            avatar: "",
+            index: "",
+            unread: 1,
+            lastSendTime: "",
+            lastContent: "",
+          },conversation
+        );
+       console.log("qxj insConversation:"+JSON.stringify(insConversation));
+       this.conversations.unshift(insConversation);
+      //  this.updateContact(insConversation);
+       //this.conversations.push(insConversation);
+      return true;
+    },
+    removeContact(id) {
+      const index = this.findContactIndexById(id);
+      if (index === -1) return false;
+      this.contacts.splice(index, 1);
+      this.CacheDraft.remove(id);
+      this.CacheMessageLoaded.remove(id);
+      return true;
+    },
+    removeConversations(conversationId) {
+      const index = this.findConversationIndexById(conversationId);
+      if (index === -1) return false;
+      this.conversations.splice(index, 1);
+      // this.CacheDraft.remove(id);
+      // this.CacheMessageLoaded.remove(id);
+      return true;
+    },
+    topPopConversations(conversation,isNotDel) {  //置顶
+      if(!isNotDel){
+          this.removeConversations(conversation.conversationId);
+      }
+      this.conversations.unshift(conversation);
+    },
+    /**
+     * 修改会话数据
+     * @param {Contact} data 修改的数据,根据 Contact.id 查找联系人并覆盖传入的值
+     */
+    updateContact(data) {
+      const conversationId = data.conversationId;
+      const index = this.findConversationIndexById(conversationId);  
+      if (index !== -1) {
+        // if(isString(data.unread)) {
+        //     if (unread.indexOf("+") === 0 || unread.indexOf("-") === 0) {
+        //       data.unread =parseInt(unread) + parseInt(this.conversations[index].unread);
+        //     }
+        // }
+        this.$set(this.conversations, index, {
+          ...this.conversations[index],
+          ...data,
+        });
+      }
+    },
+    updateContact1(data) {
+      const contactId = data.id;
+      delete data.id;
+      const index = this.findContactIndexById(contactId);  
+      if (index !== -1) {
+        const { unread } = data;
+        if (isString(unread)) {
+          if (unread.indexOf("+") === 0 || unread.indexOf("-") === 0) {
+            data.unread =parseInt(unread) + parseInt(this.contacts[index].unread);
+          }
+        }
+        this.$set(this.contacts, index, {
+          ...this.contacts[index],
+          ...data,
+        });
+      }
+    },
+    /**
+     * 根据 id 查找联系人的索引
+     * @param contactId 联系人 id
+     * @return {Number} 联系人索引,未找到返回 -1
+     */
+    findContactIndexById(contactId) {
+      return this.contacts.findIndex(item => item.id == contactId);
+    },
+    findConversationIndexById(conversationId) {
+      return this.conversations.findIndex(item => item.conversationId == conversationId);
+    },
+    /**
+     * 根据 id 查找判断是否存在联系人
+     * @param contactId 联系人 id
+     * @return {Boolean}
+     */
+    hasContact(contactId) {
+      return this.findContactIndexById(contactId) !== -1;
+    },
+    hasConversation(conversationId) {
+      return this.findConversationIndexById(conversationId) !== -1;
+    },
+    findMessage(messageId) {
+      for (const key in allMessages) {
+        const message = allMessages[key].find(({ id }) => id == messageId);
+        if (message) return message;
+      }
+    },
+    findContact(contactId) {
+      return this.getContacts().find(({ id }) => id == contactId);
+    },
+    findConversation(currentContactId) {
+       return this.conversations.find(item =>  item.conversationId == currentContactId) || {};
+    },
+    /**
+     * 返回所有联系人
+     * @return {Array<Contact>}
+     */
+    getContacts() {
+      return this.contacts;
+    },
+      /**
+     * 返回所有会话
+     * @return {Array<Contact>}
+     */
+    getConversations() {
+      return this.conversations;
+    },
+    //返回当前聊天窗口联系人信息
+    getCurrentContact() {
+      return this.currentContact;
+    },
+    getCurrentMessages() {
+      return this.currentMessages;
+    },
+    setEditorValue(val = "") {
+      if (!isString(val)) return false;
+      this.$refs.editor.setValue(this.emojiNameToImage(val));
+    },
+    getEditorValue() {
+      return this.$refs.editor.getFormatValue();
+    },
+    /**
+     * 清空某个联系人的消息,切换到该联系人时会重新触发pull-messages事件
+     */
+    clearMessages(contactId) {
+      if (contactId) {
+        delete allMessages[contactId];
+        this.CacheMessageLoaded.remove(contactId);
+        this.CacheDraft.remove(contactId);
+      } else {
+        allMessages = {};
+        this.CacheMessageLoaded.remove();
+        this.CacheDraft.remove();
+      }
+      return true;
+    },
+    /**
+     * 返回所有消息
+     * @return {Object<Contact.id,Message>}
+     */
+    getMessages(contactId) {
+      return (contactId ? allMessages[contactId] : allMessages) || [];
+    },
+    changeDrawer(params) {
+      this.drawerVisible = !this.drawerVisible;
+      if (this.drawerVisible == true) this.openDrawer(params);
+    },
+    // openDrawer(data) {
+    //   renderDrawerContent = data || new Function();
+    //   this.drawerVisible = true;
+    // },
+    openDrawer(params) {
+      renderDrawerContent = isFunction(params)
+        ? params
+        : params.render || new Function();
+      const wrapperWidth = this.$refs.wrapper.clientWidth;
+      const wrapperHeight = this.$refs.wrapper.clientHeight;
+      let width = params.width || 200;
+      let height = params.height || wrapperHeight;
+      let offsetX = params.offsetX || 0;
+      let offsetY = params.offsetY || 0;
+      const position = params.position || "right";
+      if (isString(width)) width = wrapperWidth * toPoint(width);
+      if (isString(height)) height = wrapperHeight * toPoint(height);
+      if (isString(offsetX)) offsetX = wrapperWidth * toPoint(offsetX);
+      if (isString(offsetY)) offsetY = wrapperHeight * toPoint(offsetY);
+
+      this.$refs.drawer.style.width = `${width}px`;
+      this.$refs.drawer.style.height = `${height}px`;
+
+      let left = 0;
+      let top = 0;
+      let shadow = "";
+      if (position == "right") {
+        left = wrapperWidth;
+      } else if (position == "rightInside") {
+        left = wrapperWidth - width;
+        shadow = `-15px 0 16px -14px rgba(0,0,0,0.08)`;
+      } else if (position == "center") {
+        left = wrapperWidth / 2 - width / 2;
+        top = wrapperHeight / 2 - height / 2;
+        shadow = `0 0 20px rgba(0,0,0,0.08)`;
+      }
+      left += offsetX;
+      top += offsetY + -1;
+      this.$refs.drawer.style.top = `${top}px`;
+      this.$refs.drawer.style.left = `${left}px`;
+      this.$refs.drawer.style.boxShadow = shadow;
+
+      this.drawerVisible = true;
+    },
+    closeDrawer() {
+      this.drawerVisible = false;
+    },
+  },
+};
+
+
+
+</script>
+<style lang="stylus">
+bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
+@import '../styles/utils/index'
+
++b(lemon-wrapper)
+  display flex
+  font-size 14px
+  font-family "Microsoft YaHei"
+  //mask-image radial-gradient(circle, white 100%, black 100%)
+  background #efefef
+  transition all .4s bezier
+  position relative
+  p
+    margin 0
+  img
+    vertical-align middle
+    border-style none
++b(lemon-menu)
+  flex-column()
+  align-items center
+  width 60px
+  background #1d232a
+  padding 15px 0
+  position relative
+  user-select none
+  +e(bottom)
+    flex-column()
+    position absolute
+    bottom 0
+  +e(avatar)
+    margin-bottom 20px
+    cursor pointer
+  +e(item)
+    color #999
+    cursor pointer
+    padding 14px 10px
+    max-width 100%
+    +m(active)
+      color #0fd547
+    &:hover:not(.lemon-menu__item--active)
+      color #eee
+    word-break()
+    > *
+      font-size 24px
+    .ant-badge-count
+      display inline-block
+      padding 0 4px
+      height 18px
+      line-height 16px
+      min-width 18px
+    .ant-badge-count
+    .ant-badge-dot
+      box-shadow 0 0 0 1px #1d232a
++b(lemon-sidebar)
+  width 250px
+  background #efefef
+  display flex
+  flex-direction column
+  +e(scroll)
+    overflow-y auto
+    scrollbar-light()
+  +e(label)
+    padding 6px 14px 6px 14px
+    color #666
+    font-size 12px
+    margin 0
+    text-align left
+  +b(lemon-contact--active)
+    background #d9d9d9
++b(lemon-container)
+  flex 1
+  flex-column()
+  background #f4f4f4
+  word-break()
+  position relative
+  z-index 10
+  +e(title)
+    padding 15px 15px
+  +e(displayname)
+    font-size 16px
++b(lemon-vessel)
+  display flex
+  flex 1
+  min-height 100px
+  +e(left)
+    display flex
+    flex-direction column
+    flex 1
+  +e(right)
+    flex none
++b(lemon-messages)
+  flex 1
+  height auto
++b(lemon-drawer)
+  position absolute
+  top 0
+  overflow hidden
+  background #f6f6f6
+  z-index 11
+  display none
++b(lemon-wrapper)
+  +m(drawer-show)
+    +b(lemon-drawer)
+      display block
++b(lemon-contact-info)
+  flex-column()
+  justify-content center
+  align-items center
+  height 100%
+  h4
+    font-size 16px
+    font-weight normal
+    margin 10px 0 20px 0
+    user-select none
+.lemon-wrapper--theme-blue
+  .lemon-message__content
+    background #f3f3f3
+    &::before
+      border-right-color #f3f3f3
+  .lemon-message--reverse .lemon-message__content
+    background #e6eeff
+    &::before
+      border-left-color #e6eeff
+  .lemon-container
+    background #fff
+  .lemon-sidebar
+    background #f9f9f9
+    .lemon-contact
+      background #f9f9f9
+      &:hover:not(.lemon-contact--active)
+        background #f1f1f1
+      &--active
+        background #e9e9e9
+  .lemon-menu
+    background #096bff
+  .lemon-menu__item
+    color rgba(255,255,255,0.4)
+    &:hover:not(.lemon-menu__item--active)
+      color rgba(255,255,255,0.6)
+    &--active
+      color #fff
+      text-shadow 0 0 10px rgba(2,48,118,0.4)
+.lemon-wrapper--simple
+  .lemon-menu
+  .lemon-sidebar
+    display none
+.lemon-wrapper--simple
+  .lemon-menu
+  .lemon-sidebar
+    display none
++b(lemon-contextmenu)
+  border-radius 4px
+  font-size 14px
+  font-variant tabular-nums
+  line-height 1.5
+  color rgba(0, 0, 0, 0.65)
+  z-index 9999
+  background-color #fff
+  border-radius 6px
+  box-shadow 0 2px 8px rgba(0, 0, 0, 0.06)
+  position absolute
+  transform-origin 50% 150%
+  box-sizing border-box
+  user-select none
+  overflow hidden
+  min-width 120px
+  +e(item)
+    font-size 14px
+    line-height 16px
+    padding 10px 15px
+    cursor pointer
+    display flex
+    align-items center
+    color #333
+    > span
+      display inline-block
+      flex none
+      //max-width 100px
+      ellipsis()
+    &:hover
+      background #f3f3f3
+      color #000
+    &:active
+      background #e9e9e9
+  +e(icon)
+    font-size 16px
+    margin-right 4px
+</style>

+ 1 - 31
src/components/LemonUI/components/chatrecords/segrecords.vue

@@ -22,9 +22,7 @@
             @change-conversation="handleChangeConversation"
             @message-click="handleMessageClick"
             @context-click="handleContextClick"
-            @menu-avatar-click="handleMenuAvatarClick"
-            >
-
+            @menu-avatar-click="handleMenuAvatarClick">
             <template #cover>
                     <div class="cover">
                         <!-- <i class="lemon-icon-message"></i>
@@ -33,34 +31,6 @@
                         </p> -->
                     </div>
             </template>
-
-            <template #message-title="contact">
-                <div>
-                    <div style="display:flex;justify-content:space-between">
-                        <span>{{contact.displayName}}</span>
-                        <span style="font-size:12px;">
-                        <!-- <span>打开抽屉:</span>
-                        <span style="cursor:pointer;"  @click="openDrawer('right')">
-                            右侧  | 
-                        </span>
-                        <span style="cursor:pointer;" @click="openDrawer('rightInside')">
-                            右侧内部  | 
-                        </span>
-                        <span style="cursor:pointer;" @click="openDrawer('center')">
-                            居中
-                        </span> -->
-                        </span>
-                    </div>
-                    <!-- <div v-if="contact.isGroup" class="slot-group-menu">
-                        <span>聊天</span>
-                        <span>公告</span>
-                        <span>相册</span>
-                        <span>文件</span>
-                        <span>活动</span>
-                        <span>设置(左键弹出菜单)</span> 
-                    </div> -->
-                </div>
-            </template>
         </lemon-records>
 
     </div>

+ 24 - 0
src/components/LemonUI/components/editor.vue

@@ -181,6 +181,22 @@ export default {
             return <i class="lemon-icon-image" />;
           },
         },
+        {
+          name: "msgRecords",
+          title: "聊天消息",
+          click: () => this.pickMsgBox(),
+          render: menu => {
+            return <i class="lemon-icon-message" />;
+          },
+        },
+         {
+          name: "export",
+          title: "消息导出",
+          click: () => this.exportMsg(),
+          render: menu => {
+            return <i class="lemon-icon-prompt" />;
+          },
+        },
       ];
       let tools = [];
       if (Array.isArray(this.tools)) {
@@ -188,6 +204,8 @@ export default {
           emoji: 0,
           uploadFile: 1,
           uploadImage: 2,
+          msgRecords: 3,
+          export:4,
         };
         const indexKeys = Object.keys(indexMap);
         tools = this.tools.map(item => {
@@ -286,6 +304,12 @@ export default {
             this.$emit("pickImg", this.clipboardBlob);
         }
     },
+    async pickMsgBox() {
+         this.$emit("pickMsgRecords", this.clipboardBlob);
+    },
+    async exportMsg() {
+         this.$emit("exportMsg", this.clipboardBlob);
+    },
     _handlePaste(e) {
       e.preventDefault();
       const clipboardData = e.clipboardData || window.clipboardData;

+ 2 - 0
src/components/LemonUI/index.js

@@ -17,11 +17,13 @@ import LemonMessageVideo from "./components/message/video";
 
 
 import LemonIMUI from "./components/index";
+import LemonChatUI from "./components/chatmsg";
 import LemonRecords from "./components/records";
 import "./styles/common/index.styl";
 const version = "1.4.2";
 const components = [
   LemonIMUI,
+  LemonChatUI,
   LemonContact,
   LemonMessages,
   LemonEditor,

+ 724 - 0
src/views/qw/qwChat/chatmsg.vue

@@ -0,0 +1,724 @@
+<template>
+  <div class="app-container">
+
+     <el-form class="search-form" :inline="true" >
+
+         <el-form-item label="所属部门">
+             <treeselect style="width:205.4px" v-model="deptId" :options="deptOptions" :show-count="true" placeholder="请选择所属部门" />
+         </el-form-item>
+
+          <el-form-item>
+              <el-select filterable v-model="userIds" @change="companyUserChange" placeholder="请选择员工" clearable size="small">
+                <el-option
+                  v-for="item in users"
+                  :key="item.userId"
+                  :label="item.nickName"
+                  :value="item.userId">
+                </el-option>
+              </el-select>
+          </el-form-item>
+
+         <el-form-item>
+              <el-select filterable v-model="qwUserId"  @change="qwUserChange" placeholder="请选择企微账号" clearable size="small">
+                <el-option
+                  v-for="item in accountList"
+                  :key="item.qwUserId"
+                  :label="item.account"
+                  :value="item.qwUserId">
+                </el-option>
+              </el-select>
+          </el-form-item>
+
+          <el-form-item>
+            <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+            <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+          </el-form-item>
+     </el-form>
+
+
+      <div class="imui-center qq-lemon-imui" v-show="showQW">
+        <lemon-chatui  class="lemon-slot"
+          :width="windowWidth"
+          :height="windowHeight"
+          :user="UserData"
+          ref="IMUI"  
+          :theme="theme"
+          :hide-menu="hideMenu"
+          :hide-menu-avatar="hideMenuAvatar"
+          :hide-message-name="hideMessageName"
+          :hide-message-time="hideMessageTime"
+          :messageTimeFormat="messageTimeFormat"
+          @change-menu="handleChangeMenu"
+          @pull-messages="handlePullMessages"
+          @change-contact="handleChangeContact"
+          @change-conversation="handleChangeConversation"
+          @message-click="handleMessageClick"
+          @menu-avatar-click="handleMenuAvatarClick"
+          @pick-image="handleImageClick"
+          @pick-msg-records="handleMsgRecordsClick"
+          @pick-export-msg="handleExportMsgClick"
+          >
+          <template #cover>
+                <div class="cover">
+                    <!-- <i class="lemon-icon-message"></i>
+                    <p>
+                        <b>自定义封面 Lemon</b>IMUI
+                    </p> -->
+                </div>
+          </template>
+
+        </lemon-chatui>
+      </div>
+   
+       <el-dialog :visible.sync="dialogVisible" append-to-body width="35%">
+         <img style="width:100%;height:auto" :src="dialogImageUrl" alt="" />
+      </el-dialog>
+
+       <el-dialog :visible.sync="dialogVideoVisible"
+               :close-on-click-modal="false"
+               :destroy-on-close="true"
+               :width="aplayer.pWidth"
+               :height="aplayer.height" 
+               @close="dialogVideoVisible=false"
+               ref="player">
+              <videoPlayer  :width="aplayer.width" :height="aplayer.height"  :videoWidth="aplayer.videoWidth"  :videoHeight="aplayer.videoHeight"   :vid="aplayer.vid"  :autoplay="true" 
+                :source="dialogVideoUrl" :cover="dialogVideoCover" ref="player">
+              </videoPlayer>
+       </el-dialog>
+
+       <Material ref="material" @pickImageData="handlePickImageDone"  v-model="imageArr" type="image" :num="1" :width="150" :height="150" />
+
+       <ChatRecords ref="chatRecords" :qwUser="qwUser" />
+
+       
+  </div>
+</template>
+
+<script>
+
+import store from "@/store";
+import { getQrCode } from '@/api/qw/login';
+import { getDeviceId,listAccount,getAccount} from '@/api/qw/account';
+import LemonMessageVoice from "@/components/LemonUI/components/message/voice";
+import { getContactsByUser,getConversations,getHistoryList,getConversation,sendTextMsg,sendImageMsg,getRoomInfo,exportMessage} from '@/api/qw/im';
+import Conversations from "@/components/LemonUI/database/conversations";
+import EmojiData from "@/components/LemonUI/database/emoji";
+import '@/components/LemonUI/index.css';
+import Material from '@/components/Material/index'
+import VideoPlayer from '@/components/VideoPlayer/VueAliplayer'
+import ChatRecords from '@/components/LemonUI/components/chatrecords/basic'
+
+import { getUserListByDeptId} from "@/api/company/companyUser";
+import { treeselect } from "@/api/company/companyDept";
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import { getCompanyList } from "@/api/company/company";
+
+let pages = {};
+export default {
+  name: "qwChat",
+  components: {   
+    Material,
+    VideoPlayer,
+    ChatRecords,
+    Treeselect
+  },
+  watch: {
+    // 监听deptId
+    'deptId': 'currDeptChange'
+  },
+  data() {
+    return {
+      theme: "default",
+      IMUI:null,
+      hideMenuAvatar: false,
+      hideMenu: false,
+      hideMessageName: false,
+      hideMessageTime: true,
+      accountList:[],
+      qwUser:{
+          //"userId":1688854331463712,"avatar":"https://wework.qpic.cn/wwpic/258414_QSUkkN5rTMGzwIc_1692169272/0","corpId":1970326754004217,"corpName":"重庆润方数字科技","nickName":"秦先觉","deviceId":"aeff5e72448d4d299e03d7291a21c2ea","phone":null,"account":"1261818888"
+      },
+      showQW:false,
+      UserData: {
+        id: "1000",
+        displayName: "June",
+        avatar: "",
+      },
+      contactData:null,
+      conversationData:null,
+      isFromLogin:false,
+      dialogImageUrl: '',
+      dialogVisible: false,
+      dialogVideoVisible:false, 
+      dialogVideoUrl:'',
+      dialogVideoCover:'',
+      imageArr:[],
+      pickUploadImgData:null,
+      aplayer: {
+        vid: "bf9b7e4a36d84aea8cee769765fbc28b",
+        pWidth:"1040px",
+        width:"1000px",
+        height:"900px",
+        videoWidth:"1000px",
+        videoHeight:"900px"
+        
+      },
+      player: null,
+      roomMembers:[],
+      roomAdmins:[],
+      roomInfo:null,
+      windowWidth: document.documentElement.clientWidth*0.65,  //实时屏幕宽度
+      windowHeight: document.documentElement.clientHeight*0.7,   //实时屏幕高度
+      queryParams: {
+          pageNum: 1,
+          pageSize: 10,
+          conversationId: null,
+      },
+       companys:[],
+       deptOptions:[],
+       companyId:37,
+       companyUserId:undefined,
+       deptId:undefined,
+       userIds:undefined,
+       qwUserId:undefined,
+       users:[],
+
+    };
+  },
+  created(){
+      this.getTreeselect();
+  },
+  mounted() {
+
+    this.showQW=true;
+    //this.setQwUserInfo();
+    //this.getContacts();  //获取联系人信息
+    //this.getConversation();   //获取会话信息
+   
+    const IMUI = this.$refs.IMUI;
+
+    IMUI.initMenus([
+      {
+        name: "messages",
+      },
+      // {
+      //   name: "contacts",
+      // },
+      // {
+      //   name: "custom1",
+      //   title: "自定义按钮1",
+      //   unread: 0,
+      //   render: menu => {
+      //     return <i class="lemon-icon-attah" />;
+      //   },
+      //   renderContainer: () => {
+      //     return (
+      //       <div class="article">
+      //         <ul>
+      //           <li class="article-item">
+      //             <h2>网红带货:产品真的值得买吗?</h2>
+      //           </li>
+      //           <li class="article-item">
+      //             甘肃夏河县发生5.7级地震 暂未接到人员伤亡报告
+      //           </li>
+      //           <li class="article-item">
+      //             北方多地风力仍强沙尘相伴,东北内蒙古等地迎雨雪
+      //           </li>
+      //           <li class="article-item">
+      //             英货车案:越南警方采集疑死者家属DNA作比对
+      //           </li>
+      //           <li class="article-item">
+      //             知名连锁咖啡店的蛋糕吃出活虫 曝光内幕太震惊
+      //           </li>
+      //         </ul>
+      //         <lemon-contact  props={{ contact: contactData1 }} style="margin:20px"/>
+      //         <lemon-contact props={{ contact: contactData3 }} style="margin:20px" />
+      //       </div>
+      //     );
+      //   },
+      //   isBottom: true,
+      // }
+      
+      ,
+      {
+          name: "custom2",
+          title: "切换账号",
+          unread: 0,
+          click: () => {
+            alert("拦截导航点击事件");
+          },
+          render: menu => {
+            return <i class="lemon-icon-group" />;
+          },
+          isBottom: true,
+       },
+    ]);
+    IMUI.initEmoji(EmojiData); 
+  
+  },
+
+  methods: {
+  
+    currDeptChange(val){
+      console.log(val)
+      this.deptId=val;
+      this.companyUserId=null;
+      this.getUserListByDeptId();
+    },
+    getTreeselect() {
+        treeselect().then((response) => {
+           this.deptOptions = response.data;
+        });
+    },
+    getUserListByDeptId() {
+        this.userIds=undefined;
+        var data={deptId:this.deptId};
+        getUserListByDeptId(data).then(response => {
+          this.users = response.data;
+        });
+    },
+    companyUserChange(companyUserId){  
+       this.companyUserId=companyUserId;
+       this.qwListAccount();
+    },
+    qwUserChange(qwUserId){
+        let index= this.accountList.findIndex(item => item.qwUserId == qwUserId);
+        this.qwUser=this.accountList[index];
+        this.qwUserId=this.qwUser.qwUserId;
+        this.qwUser.userId=this.qwUserId;
+        this.setQwUserInfo();
+        this.getContacts();  //获取联系人信息
+        this.getConversation();   //获取会话信息 
+    },
+    qwListAccount(companyUserId) {
+        if(!this.companyUserId){
+             this.msgError("请选择员工");
+             return;
+        }
+        let queryParams={pageNum: 1, pageSize: 10,companyUserId:this.companyUserId};
+        listAccount(queryParams).then(response => {
+            this.accountList = response.rows;
+            if(this.accountList.length>0){
+                this.qwUser=this.accountList[0];
+                this.qwUserId=this.qwUser.qwUserId;
+                this.qwUser.userId=this.qwUserId;
+                this.setQwUserInfo();
+                this.getContacts();  //获取联系人信息
+                this.getConversation();   //获取会话信息
+            }else{
+                 this.msgError("该员工暂无企微号!");
+            }
+        });
+    },
+    setQwUserInfo(){
+        this.deviceId=this.qwUser.deviceId;
+        this.UserData.id=this.qwUser.userId;
+        this.UserData.displayName=this.qwUser.nickName;
+        this.UserData.avatar=this.qwUser.avatar;
+    },
+     /** 搜索按钮操作 */
+    handleQuery() {
+      this.qwListAccount();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      //this.handleQuery();
+    },
+  
+    getRoomInfo(roomId){
+        getRoomInfo(roomId).then(response => {
+             this.roomMembers=response.members;
+             this.admins=response.members;
+             this.roomInfo=response.detail;
+        });
+    },
+    getContacts(){  
+      getContactsByUser(this.qwUser.userId).then(response => {
+           this.contactData = response.data;
+           this.$refs.IMUI.initContacts(this.contactData);
+      });
+    },
+    getConversation(){
+      let that=this;
+      const IMUI = this.$refs.IMUI;
+      getConversations(this.qwUser.userId).then(response => {
+           this.conversationData = response.data;
+           IMUI.initConversations(response.data);
+           var fstConversation=this.conversationData[0];
+           if(fstConversation){
+              IMUI.changeContact(fstConversation.conversationId);
+           }
+         
+      });
+    },
+    messageTimeFormat(time) {
+      return this.friendlyDate(time);
+    },
+    changeTheme() {
+       this.theme = this.theme == "default" ? "blue" : "default";
+    },
+    scrollToTop() {
+      document.body.scrollIntoView();
+    },
+  
+    handlePullMessages(contact, next,instance) {
+       const { IMUI } = this.$refs;
+       let isEnd = false;
+       getHistoryList(this.queryParams).then(response => {
+          if(response.code==200){
+               isEnd=response.data.isLastPage;
+               next(response.data.list, isEnd);
+               if(!isEnd){
+                   pages[contact.conversationId]++;
+                   this.queryParams.pageNum=pages[contact.conversationId];
+               }
+          }
+      });
+    },
+    handleChangeConversation(conversation, instance) {
+        //console.log("qxj ChangeConversation:"+JSON.stringify(conversation));
+        if (!pages[conversation.conversationId]){
+            pages[conversation.conversationId] =1;
+        }
+       this.queryParams.pageNum=pages[conversation.conversationId];
+       this.queryParams.conversationId=conversation.conversationId;
+       if(conversation.unread>0){
+          conversation.unread=0;
+          instance.updateContact(conversation);
+       }
+       instance.closeDrawer();
+    },
+    handleChangeContact(contact, instance) {
+        
+    },
+    //收到消息后添加消息显示
+    appendMessageAction(msgData){
+          if(msgData.type=="text" || msgData.type=="image" || msgData.type=="voice"){     //文本   text  image  video  voice 
+              const message = {
+                    id: msgData.id,
+                    status: msgData.status,
+                    type: msgData.type,
+                    sendTime: msgData.sendTime,
+                    content: msgData.content,
+                    params1: "1",
+                    params2: "2",
+                    toContactId: msgData.toContactId,
+                    fromUser: msgData.fromUser,
+              };
+              this.appendRemoteMessage(message);
+           }
+           else if(msgData.type=="file"){
+               const message = {
+                    id: msgData.id,
+                    status: msgData.status,
+                    type: msgData.type,
+                    sendTime: msgData.sendTime,
+                    content: msgData.content,
+                    toContactId: msgData.toContactId,
+                    fromUser: msgData.fromUser,
+                    fileName:msgData.fileName,
+                    fileSize:msgData.fileSize
+              };
+              this.appendRemoteMessage(message);
+           }
+           else{
+              const message = {
+                    id: msgData.id,
+                    status: msgData.status,
+                    type: msgData.type,
+                    sendTime: msgData.sendTime,
+                    content: msgData.content,
+                    toContactId: msgData.toContactId,
+                    fromUser: msgData.fromUser,
+              };
+              this.appendRemoteMessage(message);
+           }
+    },
+ 
+    handleMenuAvatarClick() {
+      console.log("Event:menu-avatar-click");
+    },
+    //聊天工具栏点击图片
+    handleImageClick() { 
+      //this.$refs.material.openMaterial(this.qwUser.deviceId);
+    },
+    handleMsgRecordsClick() { 
+      console.log("qxj handleMsgRecordsClick");
+      this.$refs.chatRecords.open(this.qwUser,this.queryParams.conversationId,this.conversationData);
+    },
+    handleExportMsgClick() { 
+      let that=this;
+      this.$confirm('是否确认导出消息记录吗?', "警告", {
+            confirmButtonText: "确定",
+            cancelButtonText: "取消",
+            type: "warning"
+          }).then(function() {
+            let queryParams={conversationId:that.queryParams.conversationId}
+            return exportMessage(queryParams);
+          }).then(response => {
+            this.download(response.msg);
+          }).catch(function() {});
+    },
+    //选择图片框确定按钮回调
+    handlePickImageDone(data){
+        console.log("handlePickImageDone:"+JSON.stringify(data));
+        this.pickUploadImgData=data;
+        const IMUI = this.$refs.IMUI;
+        IMUI._handleRemoteImage(data.url);
+    },
+    tooglePlayVideo(data){
+       this.dialogVideoVisible=true;
+       this.dialogVideoUrl=data.content;
+       this.dialogVideoCover=data.url;
+       const player = this.$refs.player.instance
+       player && player.play()
+    },
+    handleMessageClick(e, key, message, instance) {
+      console.log("点击了消息", e, key, message);
+      //console.log("qxj message:"+JSON.stringify(message));
+      if(message.type=="image"){
+           var url=!!message.url?message.url:message.content;
+           this.handlePicturePreview(url);
+      }
+      else if(message.type=="video"){  
+           this.tooglePlayVideo(message);
+      }
+      else if(message.type=="file"){  
+           
+      }
+      if (key == "status") {
+        instance.updateMessage({
+          id: message.id,
+          status: "going",
+          content: "正在重新发送消息...",
+        });
+        setTimeout(() => {
+          instance.updateMessage({
+            id: message.id,
+            status: "succeed",
+            content: "发送成功",
+          });
+        }, 2000);
+      }
+    },
+
+    changeMenuAvatarVisible() {
+      this.hideMenuAvatar = !this.hideMenuAvatar;
+    },
+    changeMenuVisible() {
+      this.hideMenu = !this.hideMenu;
+    },
+    changeMessageNameVisible() {
+      this.hideMessageName = !this.hideMessageName;
+    },
+    changeMessageTimeVisible() {
+      this.hideMessageTime = !this.hideMessageTime;
+    },
+    removeMessage() {
+        const { IMUI } = this.$refs;
+        const messages = IMUI.getCurrentMessages();
+        const id = messages[messages.length - 1].id;
+        if (messages.length > 0) {
+            IMUI.removeMessage(id);
+        }
+    },
+    updateMessage() {
+        const { IMUI } = this.$refs;
+        const messages = IMUI.getCurrentMessages();
+        const message = messages[messages.length - 1];
+        if (messages.length > 0) {
+            const update = {
+            id: message.id,
+            status: "succeed",
+            type: "file",
+            fileName: "被修改成文件了.txt",
+            fileSize: "4200000",
+            };
+            if (message.type == "event") {
+            update.fromUser = this.user;
+            }
+            IMUI.updateMessage(update);
+            IMUI.messageViewToBottom();
+      }
+    },
+    appendCustomMessage() {
+        const { IMUI } = this.$refs;
+        const message = {
+            id: generateRandId(),
+            status: "succeed",
+            type: "voice",
+            sendTime: getTime(),
+            content: "语音消息",
+            params1: "1",
+            params2: "2",
+            toContactId: "contact-1",
+            fromUser: this.user,
+        };
+        IMUI.appendMessage(message, true);
+    },
+    appendMessage() {
+        const { IMUI } = this.$refs;
+        const contact = IMUI.currentContact;
+        const message = generateMessage("contact-3");
+        message.fromUser = {
+            ...message.fromUser,
+            ...this.user,
+        };
+        IMUI.appendMessage(message, true);
+        console.log("🚀 ~ file: App.vue ~ line 1508 ~ appendMessage ~ message", message)
+    },
+    appendEventMessage() {
+      const { IMUI } = this.$refs;
+      const message = {
+        id: generateRandId(),
+        type: "event",
+        content: (
+          <span>
+            邀请你加入群聊{" "}
+            <span
+              style="color:#333;cursor:pointer"
+              on-click={() => alert("OK")}>
+              接受
+            </span>
+          </span>
+        ),
+        toContactId: "contact-3",
+        sendTime: getTime(),
+      };
+      IMUI.appendMessage(message, true);
+    },
+    appendRemoteMessage(message) {  //从服务端返回的消息
+        const { IMUI } = this.$refs;
+        IMUI.appendMessage(message, true);
+    },
+    updateContact() {
+      this.$refs.IMUI.updateContact({
+        id: "contact-3",
+        unread: 10,
+        displayName: generateRandWord(),
+        lastSendTime: getTime(),
+        lastContent: "修改昵称为随机字母",
+      });
+    },
+    handleChangeMenu() {
+      console.log("Event:change-menu");
+    },
+    openCustomContainer() {
+
+    },
+    handlePicturePreview(url) {
+        this.dialogImageUrl = url;
+        this.dialogVisible = true;
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+    .imui-center{
+        margin-bottom:"60px"; 
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        height: 80vh;
+    }
+    .lemon-wrapper{
+        border:"1px solid #ddd";
+        height: "60%" !important;
+    }
+    .lemon-drawer{
+        border:"1px solid #ddd";
+        border-left:"0";
+    }
+    .more {
+        font-size: 12px;
+        line-height: 24px;
+        height: 24px;
+        position: absolute;
+        top: 14px;
+        right: 14px;
+        cursor: pointer;
+        -webkit-user-select: none;
+        -moz-user-select: none;
+        -ms-user-select: none;
+        user-select: none;
+        color: #f1f1f1;
+        display: inline-block;
+        border-radius: 4px;
+        background: #111;
+        padding: 0 8px;
+    }
+
+    /deep/.el-dialog__headerbtn{
+         top:10px;
+    }
+
+    /deep/.el-dialog:not(.is-fullscreen) {
+        margin-top: 0 !important;
+    }
+
+    .search-form{
+       margin: 20px 30px 0px 30px;
+    }
+
+    .el-select{
+      margin: 5px 10px;
+    }
+
+    .vue-treeselect{
+      width: 217px;
+      height: 36px;
+    }
+
+  .vue-treeselect__control{
+    display: block;
+   }
+
+
+   /deep/.el-form-item{
+        margin-bottom: 0;
+        vertical-align: middle !important;
+   }
+
+  
+
+    
+</style>
+
+
+<style lang="stylus">
+   .lemon-container__title
+     padding-bottom:10px;
+   .slot-group
+      width:200px;
+      .slot-group-title
+        padding:0 0 10px 0;
+        .slot-group-notice
+           padding: 10px 10px;
+     
+      .slot-search
+           width:calc(100% - 20px) ;
+           margin:5px 10px;
+           padding:3px 0px;
+
+      .slot-group-member
+          display: flex;
+          padding: 5px 0;
+          flex:1;
+          font-size: 14px;
+          align-items: center;
+          .avatar
+              width: 30px;
+              height: 30px;
+              line-height: 30px;
+              margin-right:5px;
+              img 
+                vertical-align: middle;
+                border-style: none;
+                width: 100%;
+                height: 100%;
+                line-height: 30px;
+                border-radius: 50%;
+</style>