Huanxin Instant Messaging SDK Integration—Huanxin uni-app-demo Upgrade Plan—Overall Code Refactoring and Optimization (2)

overview

This overall refactoring of the uni-app code is based on the migration of the uni-app official website demo from the vue2 framework to the vue3 framework in the previous issue. During the migration process, there are obvious problems. The existing problems of the current project are that the code style of some parts of the project is not uniform, the naming is not standardized enough, the comments are not clear enough, and the readability is not good. If it is difficult to reuse, the local refactoring is expected to fully demonstrate the calling method of the API in the actual project, to achieve the portability of the sample code as much as possible, or to assist the secondary development of the instant messaging function.

Purpose

  • Make code more readable.

  • Simplify or remove redundant code.

  • Some components and logical renaming, re-splitting, re-merging.

  • Add global state management.

  • Modify the SDK import method to import through npm

  • Gather most APIs of the SDK into a unified file, which is convenient for management and calling.

  • Upgrade the SDK api to the latest calling method (monitoring, sending messages)

  • Add session list interface and message roaming interface.

  • SDK Ring Letter IM uni-app SDK

Refactoring plan

1. Modify the export and import usage of the original WebIM.

Purpose

  1. The existing uniSDK already supports npm import.
  2. The original instantiation code and config configuration are confusing and not clear enough.
  3. Separate initialization and configuration to form independent files for easy management.

accomplish

  1. Create an EaseIM folder in the project directory and create index.js, import the SDK in index.js and implement instantiation and export.
  2. EaseIM -> config folder and write relevant configuration in the SDK in this file and export it for instantiation.

impact (no impact)

2. Introduce pinia for state management

Pinia Documentation

Uni-app Pinia Documentation

Pinia can also complete the initialization of a store through the $reset() method. Using this method, it is very convenient to initialize the data cached in the stores when switching accounts, so as to prevent the data of the switched account from conflicting with the data of the previous account.

Purpose

  1. Store some SDK data and status (login status, session list data, message data)
  2. It is convenient for the status or data access of each component to avoid layer-by-layer data transfer.
  3. Used to replace the original local persistent data storage.
  4. It can replace the original disp publish and subscribe management tool, because the state in the store changes, each component can be recalculated or monitored, and there is no need to change the state through publish and subscribe notifications.

accomplish

  1. Introduce pinia in mian.js and mount it
//Pinia
import * as Pinia from 'pinia';
export function createApp() {
    
    
  const app = createSSRApp(App);
  app.use(Pinia.createPinia());
  return {
    
    
    app,
    Pinia,
  };
}
  1. Create new stores in the project directory and create each required store, similar directories are as follows:

impact (no impact)

3. Reorganize the code in the root component of App.vue

Purpose

  1. Simplify lengthy code in the App.vue root component of your project.
  2. Migrate the listening code in the root component.
  3. globalData, the code in the method is transferred to stores or eliminated.
  4. disp code stripping.

accomplish

  1. The listener in App.vue is embedded in the listener under the EaseIM folder for centralized management, and remounted in App.vue
  2. import '@/EaseIM'; to instantiate the SDK.
  3. Combine the data that needs to be called after the successful IM connection into one method, and call it after onConnected is triggered.
  4. Part of the code about SDK calls is moved to the imApis folder under the EaseIM folder
  5. Part of the tool method code related to the SDK is moved to the utils folder under the EaseIM folder

Influence

The changes in App.vue are relatively large, mainly for the migration of monitors, some methods are migrated to stores, and the mount of monitors needs to be remounted. The specific code can be seen in the comparison before and after the subsequent migration, or you can see the code address in github at the end of the article.

4. Optimize the login page code

Purpose

The login code of the original login component is lengthy and poorly readable, so it is optimized.

accomplish

  1. Delete the original input code and change it to two-way binding through v-model.
  2. Split the login code into two login methods: username+password and mobile phone number+verification code.
  3. Added a login storage token method to facilitate re-login in the form of user name + token when reconnecting.
  4. After the login is successful, set the login id and mobile phone number information into the stores.

impact (no impact)

5. Add home page

Purpose

  1. As the three core page container components of Conversation, Contacts, and Me, it is convenient for page switching management.
  2. Container component as Tabbar

accomplish

  1. Remove the routing configuration of the original sessions, contacts, and my (original setting page) pages.json, and add the configuration related to the routing of the home page.
  2. The home component is added to pages, and three core page components are introduced in a componentized form.
  3. Create a new layout folder in the root directory of the project and add a tabbar component, extract the tabbar functions in the three pages to the tabbar component, and add corresponding switching logic.

Influence

The main impact of this change is that it involves changing the original setting component to the Me component, and deleting the three original pages.json and introducing it in home, without any other side effects.

6. Refactor basic components such as Conversation, Contacts, and Me

Purpose

  1. Switch the source of original data (session list data, contact data, nickname avatar data) to get from SDK interface + stores.
  2. Remove the related code of disp publish and subscribe in the component, as well as the use of WebIM.
  3. Adjust unreasonable naming in the original component code, remove unused methods to simplify the component code.

accomplish

Take the Conversation component as an example

  1. Use the SDK interface getConversationlist to get the conversation list data, cache it in the stores and sort it, and use the computed property in the component to get the new conversation list data source.
  2. Since the update of the session list is dynamic, it is no longer necessary for disp to subscribe to a series of events for processing, so the relevant code can start to delete.
  3. The original jumping to the contact page or other group pages through the session list is renamed from the word "into" to "entry" and changed to "hump case".

Influence

The main impact is that the logic code in these components will have major changes in structure and data sources, which need to be verified while being modified, and will have a greater relationship with components such as stores and EaseIM, and need to be adjusted patiently.

7. Add emChatContainer component

Purpose

  1. The name of this newly added component is more semantic, and its actual function can be seen from the component name as emChat chat page component container.
  2. Merge the original singleChatEntry component and groupChatEntry component, two components with similar functions into a unified emChatContainer.

accomplish

  1. Create a new component called emChatContainer under pages, import the chat component under components with reference to the singleChatEntry component, and configure the corresponding routing path mapping in pages.
  2. Observation found that this component is used as a chat component container, mainly passing down two core parameters, 1) target ID (that is, the target ring letter ID of the chat). 2) chatType (that is, the type of target chat, usually single chat and group chat.), and these two core parameters are often used by each sub-component in the chat component, and it is cumbersome to pass down layer by layer. Therefore, if one of the parameter passing methods of Vue components is used, the provide and inject methods are used to register the parameters and pass them down.
  3. After the merger is complete, delete singleChatEntry and groupChatEntry, and point all the original method paths used to jump to this component to emChatContainer, and delete the corresponding page path in pages.json.

Influence

The routing jump path from session to chat page, from contacts and group pages to chat page is all changed to emChatContainer, and the way the chat component uses targetId (chat target ID) and chatType will be changed, because it needs to be received through inject.

8. Refactoring of emChat components

Purpose

  1. Rewrite the unreasonable file naming under this component.
  2. Remove unnecessary js files or components.
  3. Partial code refactoring is performed for each functional sub-component in this component.

accomplish

  1. Rename the chat component to emChat with emChatContainer.
  2. Delete the js files msgpackager.js, msgstorage.js, msgtype.js, pushStorage.js in its components.
  3. messagelist inputbar changed to camelCase.
  4. The source of the message list in the messageList component is changed to be obtained from stores, and the drop-down is added to obtain historical messages through getHistroyMessage.
  5. The receiving target id and message type in the subcomponent are changed to receive through inject.
  6. msgType is obtained from EaseIM/constant.
  7. All APIs for sending messages are changed to SDK4.xPromise, unified and exported in EaseIM/imApis/emMessages, imported and called in the required sending components, and the original method of sending messages is eliminated.

Influence

This component is the most difficult to adjust, because there are many components involved and codes that need to be added and adjusted. It needs to be modified and verified component by component. The specific code will be partially displayed below. For details, please refer to the source address.

9. Added a callback for prompt monitoring during reconnection

Purpose

When the IM websocket is disconnected, there will be a corresponding callback, and the corresponding prompt will be given to the user.

accomplish

Add the onReconnecting listener callback in the addEventHandler listener, and add a Toast to remind the listener that the IM is reconnecting when it is actually triggered.

PS: onReconnecting is an experimental callback.

impact (no impact)

Code snippet display before and after refactoring

1. Project directory display before and after refactoring

Project directory structure before refactoring

image.png

Project directory structure after refactoring

image.png

2. SDK introduction and initialization display before and after refactoring

Before refactoring, the SDK introduces initialization code fragments

import websdk from 'easemob-websdk/uniApp/Easemob-chat';
import config from './WebIMConfig';
console.group = console.group || {
    
    };
console.groupEnd = console.groupEnd || {
    
    };
var window = {
    
    };
let WebIM = (window.WebIM = uni.WebIM = websdk);
window.WebIM.config = config;

WebIM.conn = new WebIM.connection({
    
    
  appKey: WebIM.config.appkey,
  url: WebIM.config.xmppURL,
  apiUrl: WebIM.config.apiURL,
});

export default WebIM;

After refactoring, the SDK introduces initialization code fragments

import EaseSDK from 'easemob-websdk/uniApp/Easemob-chat';
import {
    
     EM_APP_KEY, EM_API_URL, EM_WEB_SOCKET_URL } from './config';
let EMClient = (uni.EMClient = {
    
    });
EMClient = new EaseSDK.connection({
    
    
  appKey: EM_APP_KEY,
  apiUrl: EM_API_URL,
  url: EM_WEB_SOCKET_URL,
});
uni.EMClient = EMClient;
export {
    
     EaseSDK, EMClient };

3. App.vue component code snippet display before and after refactoring

The component code is too long, in order to prevent hydrological suspicion, so intercept part of the code display [manual dog head]

App.vue component code snippet before refactoring

<script>
import WebIM from '@/utils/WebIM.js';
import msgStorage from '@/components/chat/msgstorage';
import _chunkArr from './utils/chunkArr';
import msgType from '@/components/chat/msgtype';
import disp from '@/utils/broadcast';
import {
    
     onGetSilentConfig } from './components/chat/pushStorage';
let logout = false;

function ack(receiveMsg) {
    
    
  // 处理未读消息回执
  var bodyId = receiveMsg.id; // 需要发送已读回执的消息id

  var ackMsg = new WebIM.message('read', WebIM.conn.getUniqueId());
  ackMsg.set({
    
    
    id: bodyId,
    to: receiveMsg.from,
  });
  WebIM.conn.send(ackMsg.body);
}

function onMessageError(err) {
    
    
  if (err.type === 'error') {
    
    
    uni.showToast({
    
    
      title: err.errorText,
    });
    return false;
  }

  return true;
}

function getCurrentRoute() {
    
    
  let pages = getCurrentPages();
  if (pages.length > 0) {
    
    
    let currentPage = pages[pages.length - 1];
    return currentPage.route;
  }
  return '/';
}

// 包含陌生人版本
//该方法用以计算本地存储消息的未读总数。
function calcUnReadSpot(message) {
    
    
  let myName = uni.getStorageSync('myUsername');
  let pushObj = uni.getStorageSync('pushStorageData');
  let pushAry = pushObj[myName] || [];
  uni.getStorageInfo({
    
    
    success: function (res) {
    
    
      let storageKeys = res.keys;
      let newChatMsgKeys = [];
      let historyChatMsgKeys = [];
      storageKeys.forEach((item) => {
    
    
        if (item.indexOf(myName) > -1 && item.indexOf('rendered_') == -1) {
    
    
          newChatMsgKeys.push(item);
        }
      });
      let count = newChatMsgKeys.reduce(function (result, curMember, idx) {
    
    
        let newName = curMember.split(myName)[0];
        let chatMsgs;
        chatMsgs = uni.getStorageSync(curMember) || [];
        //过滤消息来源与当前登录ID一致的消息,不计入总数中。
        chatMsgs = chatMsgs.filter((msg) => msg.yourname !== myName);
        if (pushAry.includes(newName)) return result;
        return result + chatMsgs.length;
      }, 0);
      getApp().globalData.unReadMessageNum = count;
      disp.fire('em.unreadspot', message);
    },
  });
}

function saveGroups() {
    
    
  var me = this;
  return WebIM.conn.getGroup({
    
    
    limit: 50,
    success: function (res) {
    
    
      uni.setStorage({
    
    
        key: 'listGroup',
        data: res.data,
      });
    },
    error: function (err) {
    
    
      console.log(err);
    },
  });
}

export default {
    
    
  globalData: {
    
    
    phoneNumber: '',
    unReadMessageNum: 0,
    userInfo: null,
    userInfoFromServer: null, //用户属性从环信服务器获取
    friendUserInfoMap: new Map(), //好友属性
    saveFriendList: [],
    saveGroupInvitedList: [],
    isIPX: false, //是否为iphone X
    conn: {
    
    
      closed: false,
      curOpenOpt: {
    
    },

      open(opt) {
    
    
        uni.showLoading({
    
    
          title: '正在初始化客户端..',
          mask: true,
        });
        const actionOpen = () => {
    
    
          this.curOpenOpt = opt;
          WebIM.conn
            .open(opt)
            .then(() => {
    
    
              //token获取成功,即可开始请求用户属性。
              disp.fire('em.mian.profile.update');
              disp.fire('em.mian.friendProfile.update');
            })
            .catch((err) => {
    
    
              console.log('>>>>>token获取失败', err);
            });
          this.closed = false;
        };
        if (WebIM.conn.isOpened()) {
    
    
          WebIM.conn.close();
          setTimeout(() => {
    
    
            actionOpen();
          }, 300);
        } else {
    
    
          actionOpen();
        }
      },

      reopen() {
    
    
        if (this.closed) {
    
    
          //this.open(this.curOpenOpt);
          WebIM.conn.open(this.curOpenOpt);
          this.closed = false;
        }
      },
    },
    onLoginSuccess: function (myName) {
    
    
      uni.hideLoading();
      uni.redirectTo({
    
    
        url: '../conversation/conversation?myName=' + myName,
      });
    },
  onLaunch() {
    
    
    var me = this;
    var logs = uni.getStorageSync('logs') || [];
    logs.unshift(Date.now());
    uni.setStorageSync('logs', logs);

    disp.on('em.main.ready', function () {
    
    
      calcUnReadSpot();
    });
    uni.WebIM.conn.listen({
    
    
      onOpened(message) {
    
    
        if (
          getCurrentRoute() == 'pages/login/login' ||
          getCurrentRoute() == 'pages/login_token/login_token'
        ) {
    
    
          me.globalData.onLoginSuccess(
            uni.getStorageSync('myUsername').toLowerCase()
          );
          me.fetchFriendListFromServer();
        }
      },

      onReconnect() {
    
    
        uni.showToast({
    
    
          title: '重连中...',
          duration: 2000,
        });
      },

      onSocketConnected() {
    
    
        uni.showToast({
    
    
          title: 'socket连接成功',
          duration: 2000,
        });
      },

      onClosed() {
    
    
        uni.showToast({
    
    
          title: '退出登录',
          icon: 'none',
          duration: 2000,
        });
        uni.redirectTo({
    
    
          url: '../login/login',
        });
        me.globalData.conn.closed = true;
        WebIM.conn.close();
      },
      onTextMessage(message) {
    
    
        console.log('onTextMessage', message);

        if (message) {
    
    
          if (onMessageError(message)) {
    
    
            msgStorage.saveReceiveMsg(message, msgType.TEXT);
          }

          calcUnReadSpot(message);
          ack(message);
          onGetSilentConfig(message);
        }
      },
      onPictureMessage(message) {
    
    
        console.log('onPictureMessage', message);

        if (message) {
    
    
          if (onMessageError(message)) {
    
    
            msgStorage.saveReceiveMsg(message, msgType.IMAGE);
          }

          calcUnReadSpot(message);
          ack(message);
          onGetSilentConfig(message);
        }
      },
    });
    this.globalData.checkIsIPhoneX();
  },

  methods: {
    
    
    async fetchUserInfoWithLoginId() {
    
    
      const userId = await uni.WebIM.conn.user;
      if (userId) {
    
    
        try {
    
    
          const {
    
     data } = await uni.WebIM.conn.fetchUserInfoById(userId);
          this.globalData.userInfoFromServer = Object.assign({
    
    }, data[userId]);
        } catch (error) {
    
    
          console.log(error);
          uni.showToast({
    
    
            title: '用户属性获取失败',
            icon: 'none',
            duration: 2000,
          });
        }
      }
    },
    async fetchFriendInfoFromServer() {
    
    
      let friendList = [];
      try {
    
    
        const res = await uni.WebIM.conn.getContacts();
        friendList = Object.assign([], res?.data);
        if (friendList.length && friendList.length < 99) {
    
    
          const {
    
     data } = await uni.WebIM.conn.fetchUserInfoById(friendList);
          this.setFriendUserInfotoMap(data);
        } else {
    
    
          let newArr = _chunkArr(friendList, 99);
          for (let i = 0; i < newArr.length; i++) {
    
    
            const {
    
     data } = await uni.WebIM.conn.fetchUserInfoById(newArr[i]);
            this.setFriendUserInfotoMap(data);
          }
        }
      } catch (error) {
    
    
        console.log(error);
        uni.showToast({
    
    
          title: '用户属性获取失败',
          icon: 'none',
        });
      }
    },
    setFriendUserInfotoMap(data) {
    
    
      if (Object.keys(data).length) {
    
    
        for (const key in data) {
    
    
          if (Object.hasOwnProperty.call(data, key)) {
    
    
            const values = data[key];
            Object.values(values).length &&
              this.globalData.friendUserInfoMap.set(key, values);
          }
        }
      }
    },
    async fetchFriendListFromServer() {
    
    
      uni.removeStorageSync('member');
      try {
    
    
        const {
    
     data } = await WebIM.conn.getContacts();
        console.log('>>>>>>App.vue 拉取好友列表');
        if (data.length) {
    
    
          uni.setStorage({
    
    
            key: 'member',
            data: [...data],
          });
        }
      } catch (error) {
    
    
        console.log('>>>>>好友列表获取失败', error);
      }
    },
  },
};
</script>
<style lang="scss">
@import './app.css';
</style>

App.vue component code snippet after refactoring

It can be seen that the code is significantly simplified compared to the original App.vue component.

<script>
/* EaseIM */
import '@/EaseIM';
import {
    
     emConnectListener, emMountGlobalListener } from '@/EaseIM/listener';
import {
    
     emUserInfos, emGroups, emContacts } from '@/EaseIM/imApis';
import {
    
     CONNECT_CALLBACK_TYPE } from '@/EaseIM/constant';
import {
    
     useLoginStore } from '@/stores/login';
import {
    
     useGroupStore } from '@/stores/group';
import {
    
     useContactsStore } from '@/stores/contacts';
import {
    
     EMClient } from './EaseIM';

export default {
    
    
  setup() {
    
    
    const loginStore = useLoginStore();
    const groupStore = useGroupStore();
    const contactsStore = useContactsStore();
    /* 链接所需监听回调 */
    //传给监听callback回调
    const connectedCallback = (type) => {
    
    
      console.log('>>>>>连接成功回调', type);
      if (type === CONNECT_CALLBACK_TYPE.CONNECT_CALLBACK) {
    
    
        onConnectedSuccess();
      }
      if (type === CONNECT_CALLBACK_TYPE.DISCONNECT_CALLBACK) {
    
    
        onDisconnect();
      }
      if (type === CONNECT_CALLBACK_TYPE.RECONNECTING_CALLBACK) {
    
    
        onReconnecting();
      }
    };
    //IM连接成功
    const onConnectedSuccess = () => {
    
    
      const loginUserId = loginStore.loginUserBaseInfos.loginUserId;
      if (!loginStore.loginStatus) {
    
    
        fetchLoginUserNeedData();
      }
      loginStore.setLoginStatus(true);
      uni.hideLoading();
      uni.redirectTo({
    
    
        url: '../home/index?myName=' + loginUserId,
      });
    };
    //IM断开连接
    const onDisconnect = () => {
    
    
      //断开回调触发后,如果业务登录状态为true则说明异常断开需要重新登录
      if (!loginStore.loginStatus) {
    
    
        uni.showToast({
    
    
          title: '退出登录',
          icon: 'none',
          duration: 2000,
        });
        uni.redirectTo({
    
    
          url: '../login/login',
        });
        EMClient.close();
      } else {
    
    
        //执行通过token机型重新登录
        const loginUserId = uni.getStorageSync('myUsername');
        const loginUserToken =
          loginUserId && uni.getStorageSync(`EM_${
      
      loginUserId}_TOKEN`);
        EMClient.open({
    
     user: loginUserId, accessToken: loginUserToken.token });
      }
    };
    //IM重连中
    const onReconnecting = () => {
    
    
      uni.showToast({
    
    
        title: 'IM 重连中...',
        icon: 'none',
      });
    };
    //挂载IM websocket连接成功监听
    emConnectListener(connectedCallback);
    const {
    
     fetchUserInfoWithLoginId, fetchOtherInfoFromServer } =
      emUserInfos();
    const {
    
     fetchJoinedGroupListFromServer } = emGroups();
    const {
    
     fetchContactsListFromServer } = emContacts();
    //获取登录所需基础参数
    const fetchLoginUserNeedData = async () => {
    
    
      //获取好友列表
      const friendList = await fetchContactsListFromServer();
      await contactsStore.setFriendList(friendList);
      //获取群组列表
      const joinedGroupList = await fetchJoinedGroupListFromServer();
      joinedGroupList.length &&
        (await groupStore.setJoinedGroupList(joinedGroupList));
      if (friendList.length) {
    
    
        //获取好友用户属性
        const friendProfiles = await fetchOtherInfoFromServer(friendList);
        contactsStore.setFriendUserInfotoMap(friendProfiles);
      }
      //获取当前登录用户好友信息
      const profiles = await fetchUserInfoWithLoginId();
      await loginStore.setLoginUserProfiles(profiles[EMClient.user]);
    };
    //挂载全局所需监听回调【好友关系、消息监听、群组监听】
    emMountGlobalListener();
  },
};
</script>
<style lang="scss">
@import './app.css';
</style>

4. Comparison of code fragments of the conversation list (conversation) component before and after refactoring

Code snippet display of session list before refactoring

PS: The code in the template has not changed much, and the template-related code is temporarily omitted to reduce the length

<script setup>
import {
    
     reactive, computed } from 'vue';
import {
    
     onLoad, onShow, onUnload } from '@dcloudio/uni-app';
import swipeDelete from '@/components/swipedelete/swipedelete';
import msgtype from '@/components/chat/msgtype';
import dateFormater from '@/utils/dateFormater';
import disp from '@/utils/broadcast';
const WebIM = uni.WebIM;
let isfirstTime = true;
const conversationState = reactive({
    
    
  //       msgtype,
  search_btn: true,
  search_chats: false,
  show_mask: false,
  yourname: '',
  unReadSpotNum: 0,
  unReadNoticeNum: 0,
  messageNum: 0,
  unReadTotalNotNum: 0,
  conversationList: [],
  show_clear: false,
  member: '',
  isIPX: false,
  gotop: false,
  input_code: '',
  groupName: {
    
    },
  winSize: {
    
    },
  popButton: ['删除该聊天'],
  showPop: false,
  currentVal: '',
  pushConfigData: [],
  defaultAvatar: '/static/images/theme2x.png',
  defaultGroupAvatar: '/static/images/groupTheme.png',
});
onLoad(() => {
    
    
  disp.on('em.subscribe', onChatPageSubscribe);
  //监听解散群
  disp.on('em.invite.deleteGroup', onChatPageDeleteGroup);
  //监听未读消息数
  disp.on('em.unreadspot', onChatPageUnreadspot);
  //监听未读加群“通知”
  disp.on('em.invite.joingroup', onChatPageJoingroup);
  //监听好友删除
  disp.on('em.contacts.remove', onChatPageRemoveContacts);
  //监听好友关系解除
  disp.on('em.unsubscribed', onChatPageUnsubscribed);
  if (!uni.getStorageSync('listGroup')) {
    
    
    listGroups();
  }
  if (!uni.getStorageSync('member')) {
    
    
    getRoster();
  }

  readJoinedGroupName();
});
onShow(() => {
    
    
  uni.hideHomeButton && uni.hideHomeButton();
  setTimeout(() => {
    
    
    getLocalConversationlist();
  }, 100);
  conversationState.unReadMessageNum =
    getApp().globalData.unReadMessageNum > 99
      ? '99+'
      : getApp().globalData.unReadMessageNum;
  conversationState.messageNum = getApp().globalData.saveFriendList.length;
  conversationState.unReadNoticeNum =
    getApp().globalData.saveGroupInvitedList.length;
  conversationState.unReadTotalNotNum =
    getApp().globalData.saveFriendList.length +
    getApp().globalData.saveGroupInvitedList.length;
  if (getApp().globalData.isIPX) {
    
    
    conversationState.isIPX = true;
  }
});
const showConversationAvatar = computed(() => {
    
    
  const friendUserInfoMap = getApp().globalData.friendUserInfoMap;
  return (item) => {
    
    
    if (item.chatType === 'singleChat' || item.chatType === 'chat') {
    
    
      if (
        friendUserInfoMap.has(item.username) &&
        friendUserInfoMap.get(item.username)?.avatarurl
      ) {
    
    
        return friendUserInfoMap.get(item.username).avatarurl;
      } else {
    
    
        return conversationState.defaultAvatar;
      }
    } else if (
      item.chatType.toLowerCase() === 'groupchat' ||
      item.chatType === 'chatRoom'
    ) {
    
    
      return conversationState.defaultGroupAvatar;
    }
  };
});
const showConversationName = computed(() => {
    
    
  const friendUserInfoMap = getApp().globalData.friendUserInfoMap;
  return (item) => {
    
    
    if (item.chatType === 'singleChat' || item.chatType === 'chat') {
    
    
      if (
        friendUserInfoMap.has(item.username) &&
        friendUserInfoMap.get(item.username)?.nickname
      ) {
    
    
        return friendUserInfoMap.get(item.username).nickname;
      } else {
    
    
        return item.username;
      }
    } else if (
      item.chatType === msgtype.chatType.GROUP_CHAT ||
      item.chatType === msgtype.chatType.CHAT_ROOM ||
      item.chatType === 'groupchat'
    ) {
    
    
      return item.groupName;
    }
  };
});
const handleTime = computed(() => {
    
    
  return (item) => {
    
    
    return dateFormater('MM/DD/HH:mm', item.time);
  };
});

const listGroups = () => {
    
    
  return uni.WebIM.conn.getGroup({
    
    
    limit: 50,
    success: function (res) {
    
    
      uni.setStorage({
    
    
        key: 'listGroup',
        data: res.data,
      });
      readJoinedGroupName();
      getLocalConversationlist();
    },
    error: function (err) {
    
    
      console.log(err);
    },
  });
};

const getRoster = async () => {
    
    
  const {
    
     data } = await WebIM.conn.getContacts();
  if (data.length) {
    
    
    uni.setStorage({
    
    
      key: 'member',
      data: [...data],
    });
    conversationState.member = [...data];
    //if(!systemReady){
    
    
    disp.fire('em.main.ready');
    //systemReady = true;
    //}
    getLocalConversationlist();
    conversationState.unReadSpotNum =
      getApp().globalData.unReadMessageNum > 99
        ? '99+'
        : getApp().globalData.unReadMessageNum;
  }
  console.log('>>>>好友列表获取成功', data);
};

const readJoinedGroupName = () => {
    
    
  const joinedGroupList = uni.getStorageSync('listGroup');
  const groupList = joinedGroupList?.data || joinedGroupList || [];
  let groupName = {
    
    };
  groupList.forEach((item) => {
    
    
    groupName[item.groupid] = item.groupname;
  });
  conversationState.groupName = groupName;
};
// 包含陌生人版本
const getLocalConversationlist = () => {
    
    
  const myName = uni.getStorageSync('myUsername');
  uni.getStorageInfo({
    
    
    success: (res) => {
    
    
      let storageKeys = res.keys;
      let newChatMsgKeys = [];
      let historyChatMsgKeys = [];
      let len = myName.length;
      storageKeys.forEach((item) => {
    
    
        if (item.slice(-len) == myName && item.indexOf('rendered_') == -1) {
    
    
          newChatMsgKeys.push(item);
        } else if (
          item.slice(-len) == myName &&
          item.indexOf('rendered_') > -1
        ) {
    
    
          historyChatMsgKeys.push(item);
        } else if (item === 'INFORM') {
    
    
          newChatMsgKeys.push(item);
        }
      });
      packageConversation(newChatMsgKeys, historyChatMsgKeys);
    },
  });
};
//组件会话列表方法
const packageConversation = (newChatMsgKeys, historyChatMsgKeys) => {
    
    
  const myName = uni.getStorageSync('myUsername');
  let conversationList = [];
  let lastChatMsg; //最后一条消息
  for (let i = historyChatMsgKeys.length; i > 0, i--; ) {
    
    
    let index = newChatMsgKeys.indexOf(historyChatMsgKeys[i].slice(9));
    if (index > -1) {
    
    
      let newChatMsgs = uni.getStorageSync(newChatMsgKeys[index]) || [];
      if (newChatMsgs.length) {
    
    
        lastChatMsg = newChatMsgs[newChatMsgs.length - 1];
        lastChatMsg.unReadCount = newChatMsgs.length;
        newChatMsgKeys.splice(index, 1);
      } else {
    
    
        let historyChatMsgs = uni.getStorageSync(historyChatMsgKeys[i]);
        if (historyChatMsgs.length) {
    
    
          lastChatMsg = historyChatMsgs[historyChatMsgs.length - 1];
        }
      }
    } else {
    
    
      let historyChatMsgs = uni.getStorageSync(historyChatMsgKeys[i]);
      if (historyChatMsgs.length) {
    
    
        lastChatMsg = historyChatMsgs[historyChatMsgs.length - 1];
      }
    }
    if (
      lastChatMsg.chatType == msgtype.chatType.GROUP_CHAT ||
      lastChatMsg.chatType == msgtype.chatType.CHAT_ROOM ||
      lastChatMsg.chatType == 'groupchat'
    ) {
    
    
      lastChatMsg.groupName = conversationState.groupName[lastChatMsg.info.to];
    }
    lastChatMsg &&
      lastChatMsg.username != myName &&
      conversationList.push(lastChatMsg);
  }
  for (let i = newChatMsgKeys.length; i > 0, i--; ) {
    
    
    let newChatMsgs = uni.getStorageSync(newChatMsgKeys[i]) || [];
    if (newChatMsgs.length) {
    
    
      lastChatMsg = newChatMsgs[newChatMsgs.length - 1];
      lastChatMsg.unReadCount = newChatMsgs.length;
      if (
        lastChatMsg.chatType == msgtype.chatType.GROUP_CHAT ||
        lastChatMsg.chatType == msgtype.chatType.CHAT_ROOM ||
        lastChatMsg.chatType == 'groupchat'
      ) {
    
    
        lastChatMsg.groupName =
          conversationState.groupName[lastChatMsg.info.to];
      }
      lastChatMsg.username != myName && conversationList.push(lastChatMsg);
    }
  }
  conversationList.sort((a, b) => {
    
    
    return b.time - a.time;
  });
  console.log('>>>>>>conversationList', conversationList);
  conversationState.conversationList = conversationList;
};
const openSearch = () => {
    
    
  conversationState.search_btn = false;
  conversationState.search_chats = true;
  conversationState.gotop = true;
};
const onSearch = (val) => {
    
    
  let searchValue = val.detail.value;
  var myName = uni.getStorageSync('myUsername');
  let serchList = [];
  let conversationList = [];
  uni.getStorageInfo({
    
    
    success: function (res) {
    
    
      let storageKeys = res.keys;
      let chatKeys = [];
      let len = myName.length;
      storageKeys.forEach((item) => {
    
    
        if (item.slice(-len) == myName) {
    
    
          chatKeys.push(item);
        }
      });
      chatKeys.forEach((item, index) => {
    
    
        if (item.indexOf(searchValue) != -1) {
    
    
          serchList.push(item);
        }
      });
      let lastChatMsg = '';
      serchList.forEach((item, index) => {
    
    
        let chatMsgs = uni.getStorageSync(item) || [];
        if (chatMsgs.length) {
    
    
          lastChatMsg = chatMsgs[chatMsgs.length - 1];
          conversationList.push(lastChatMsg);
        }
      });
      conversationState.conversationList = conversationList;
    },
  });
};
const cancel = () => {
    
    
  getLocalConversationlist();
  conversationState.search_btn = true;
  conversationState.search_chats = false;
  conversationState.unReadSpotNum =
    getApp().globalData.unReadMessageNum > 99
      ? '99+'
      : getApp().globalData.unReadMessageNum;
  conversationState.gotop = false;
};
const clearInput = () => {
    
    
  conversationState.input_code = '';
  conversationState.show_clear = false;
};
const onInput = (e) => {
    
    
  let inputValue = e.detail.value;
  if (inputValue) {
    
    
    conversationState.show_clear = true;
  } else {
    
    
    conversationState.show_clear = false;
  }
};
const tab_contacts = () => {
    
    
  uni.redirectTo({
    
    
    url: '../main/main?myName=' + uni.getStorageSync('myUsername'),
  });
};
const close_mask = () => {
    
    
  conversationState.search_btn = true;
  conversationState.search_chats = false;
  conversationState.show_mask = false;
};
const tab_setting = () => {
    
    
  uni.redirectTo({
    
    
    url: '../setting/setting',
  });
};
const tab_notification = () => {
    
    
  uni.redirectTo({
    
    
    url: '../notification/notification',
  });
};
const into_chatRoom = (event) => {
    
    
  let detail = JSON.parse(event.currentTarget.dataset.item);
  if (
    detail.chatType == msgtype.chatType.GROUP_CHAT ||
    detail.chatType == msgtype.chatType.CHAT_ROOM ||
    detail.groupName
  ) {
    
    
    into_groupChatRoom(detail);
  } else {
    
    
    into_singleChatRoom(detail);
  }
};
// 单聊
const into_singleChatRoom = (detail) => {
    
    
  var myName = uni.getStorageSync('myUsername');
  var nameList = {
    
    
    myName: myName,
    your: detail.username,
  };
  const friendUserInfoMap = getApp().globalData.friendUserInfoMap;
  if (
    friendUserInfoMap.has(nameList.your) &&
    friendUserInfoMap.get(nameList.your)?.nickname
  ) {
    
    
    nameList.yourNickName = friendUserInfoMap.get(nameList.your).nickname;
  }
  uni.navigateTo({
    
    
    url:
      '../singleChatEntry/singleChatEntry?username=' + JSON.stringify(nameList),
  });
};
// 群聊 和 聊天室 (两个概念)
const into_groupChatRoom = (detail) => {
    
    
  var myName = uni.getStorageSync('myUsername');
  var nameList = {
    
    
    myName: myName,
    your: detail.groupName,
    groupId: detail.info.to,
  };
  uni.navigateTo({
    
    
    url:
      '../groupChatEntry/groupChatEntry?username=' + JSON.stringify(nameList),
  });
};
const into_inform = () => {
    
    
  uni.redirectTo({
    
    
    url: '../notification/notification',
  });
};

const removeAndRefresh = (event) => {
    
    
  let removeId = event.currentTarget.dataset.item.info.from;
  let ary = getApp().globalData.saveFriendList;
  let idx;
  if (ary.length > 0) {
    
    
    ary.forEach((v, k) => {
    
    
      if (v.from == removeId) {
    
    
        idx = k;
      }
    });
    getApp().globalData.saveFriendList.splice(idx, 1);
  }
  uni.removeStorageSync('INFORM');
};

const del_chat = (event) => {
    
    
  let detail = event.currentTarget.dataset.item;
  let nameList = {
    
    };
  // 删除当前选中群组聊天列表
  if (detail.chatType == 'groupchat' || detail.chatType == 'chatRoom') {
    
    
    nameList = {
    
    
      your: detail.info.to,
    };
    //删除当前选中通知列表
  } else if (detail.chatType === 'INFORM') {
    
    
    nameList = {
    
    
      your: 'INFORM',
    };
  }
  //删除当前选中好友聊天列表
  else {
    
    
    nameList = {
    
    
      your: detail.username,
    };
  }
  var myName = uni.getStorageSync('myUsername');
  var currentPage = getCurrentPages();

  uni.showModal({
    
    
    title: '确认删除?',
    confirmText: '删除',
    success: function (res) {
    
    
      if (res.confirm) {
    
    
        uni.removeStorageSync(nameList.your + myName);
        uni.removeStorageSync('rendered_' + nameList.your + myName);
        nameList.your === 'INFORM' && removeAndRefresh(event);
        // if (Object.keys(currentPage[0]).length>0) {
    
    
        //   currentPage[0].onShow();
        // }
        disp.fire('em.chat.session.remove');
        getLocalConversationlist();
      }
    },
    fail: function (err) {
    
    
      console.log('删除列表', err);
    },
  });
};
const removeLocalStorage = (yourname) => {
    
    
  var myName = uni.getStorageSync('myUsername');
  uni.removeStorageSync(yourname + myName);
  uni.removeStorageSync('rendered_' + yourname + myName);
};

/* 获取窗口尺寸 */
const getWindowSize = () => {
    
    
  uni.getSystemInfo({
    
    
    success: (res) => {
    
    
      conversationState.winSize = {
    
    
        witdh: res.windowWidth,
        height: res.windowHeight,
      };
    },
  });
};
const hidePop = () => {
    
    
  conversationState.showPop = false;
};
const pickerMenuChange = () => {
    
    
  del_chat(conversationState.currentVal);
};

/*  disp event callback function */
const onChatPageSubscribe = () => {
    
    
  getLocalConversationlist();
  conversationState.messageNum = getApp().globalData.saveFriendList.length;
  conversationState.unReadTotalNotNum =
    getApp().globalData.saveFriendList.length +
    getApp().globalData.saveGroupInvitedList.length;
};
const onChatPageDeleteGroup = (infos) => {
    
    
  listGroups();
  getRoster();
  getLocalConversationlist();
  conversationState.messageNum = getApp().globalData.saveFriendList.length;
  //如果会话存在则执行删除会话
  removeLocalStorage(infos.gid);
};
const onChatPageUnreadspot = (message) => {
    
    
  getLocalConversationlist();
  let currentLoginUser = WebIM.conn.context.userId;
  let id =
    message && message.chatType === 'groupchat' ? message?.to : message?.from;
  let pushObj = uni.getStorageSync('pushStorageData');
  let pushAry = pushObj[currentLoginUser] || [];
  conversationState.pushConfigData = pushAry;

  // if (message && pushValue.includes(id)) return
  conversationState.unReadSpotNum =
    getApp().globalData.unReadMessageNum > 99
      ? '99+'
      : getApp().globalData.unReadMessageNum;
};
const onChatPageJoingroup = () => {
    
    
  conversationState.unReadMessageNum =
    getApp().globalData.saveGroupInvitedList.length;
  conversationState.unReadTotalNotNum =
    getApp().globalData.saveFriendList.length +
    getApp().globalData.saveGroupInvitedList.length;
  getLocalConversationlist();
};
const onChatPageRemoveContacts = () => {
    
    
  getLocalConversationlist();
  getRoster();
};
const onChatPageUnsubscribed = (message) => {
    
    
  uni.showToast({
    
    
    title: `${
      
      message.from}好友关系解除`,
    icon: 'none',
  });
};
onUnload(() => {
    
    
  //页面卸载同步取消onload中的订阅,防止重复订阅事件。
  disp.off('em.subscribe', conversationState.onChatPageSubscribe);
  disp.off('em.invite.deleteGroup', conversationState.onChatPageDeleteGroup);
  disp.off('em.unreadspot', conversationState.onChatPageUnreadspot);
  disp.off('em.invite.joingroup', conversationState.onChatPageJoingroup);
  disp.off('em.contacts.remove', conversationState.onChatPageRemoveContacts);
  disp.off('em.unsubscribed', conversationState.onChatPageUnsubscribed);
});
</script>
<style>
@import './conversation.css';
</style>

Code snippet display of session list after refactoring

<script setup>
import {
    
     reactive, computed, watch, watchEffect } from 'vue';
import {
    
     onLoad, onShow } from '@dcloudio/uni-app';
import swipeDelete from '@/components/swipedelete/swipedelete';
import {
    
     emConversation } from '@/EaseIM/imApis';
import {
    
     CHAT_TYPE, MESSAGE_TYPE } from '@/EaseIM/constant';
import {
    
     useConversationStore } from '@/stores/conversation';
import {
    
     useContactsStore } from '@/stores/contacts';
import {
    
     useGroupStore } from '@/stores/group';
import dateFormater from '@/utils/dateFormater';
/* store */
import {
    
     useInformStore } from '@/stores/inform';
const conversationState = reactive({
    
    
  search_btn: true,
  search_chats: false,
  search_keyword: '',
  show_mask: false,
  yourname: '',
  unReadSpotNum: 0,
  unReadNoticeNum: 0,
  messageNum: 0,
  unReadTotalNotNum: 0,
  conversationList: [], //搜索后返回的会话数据,
  show_clear: false,
  member: '',
  isIPX: false,
  gotop: false,
  groupName: {
    
    },
  winSize: {
    
    },
  popButton: ['删除该聊天'],
  showPop: false,
  currentVal: '',
  pushConfigData: [],
  defaultAvatar: '/static/images/theme2x.png',
  defaultGroupAvatar: '/static/images/groupTheme.png',
});
//群组名称
const groupStore = useGroupStore();
const getGroupName = (groupid) => {
    
    
  const joinedGroupList = groupStore.joinedGroupList;
  let groupName = '';
  if (joinedGroupList.length) {
    
    
    joinedGroupList.forEach((item) => {
    
    
      if (item.groupid === groupid) {
    
    
        console.log(item.groupname);
        return (groupName = item.groupname);
      }
    });
    return groupName;
  } else {
    
    
    return groupid;
  }
};
/* 系统通知 */
const informStore = useInformStore();
//最近一条系统通知
const lastInformData = computed(() => {
    
    
  return (
    informStore.getAllInformsList[informStore.getAllInformsList.length - 1] ||
    null
  );
});
//未处理系统通知总数
const unReadNoticeNum = computed(() => {
    
    
  return informStore.getAllInformsList.filter((inform) => !inform.isHandled)
    .length;
});
/* 会话列表 */
const conversationStore = useConversationStore();
const {
    
    
  fetchConversationFromServer,
  removeConversationFromServer,
  sendChannelAck,
} = emConversation();
const fetchConversationList = async () => {
    
    
  const res = await fetchConversationFromServer();
  if (res?.data?.channel_infos) {
    
    
    conversationStore.setConversationList(
      Object.assign([], res.data.channel_infos)
    );
  }
};
//会话列表数据
const conversationList = computed(() => {
    
    
  return conversationStore.sortedConversationList;
});
watchEffect(() => {
    
    
  console.log('>>>>>执行更新会话列表数据');
  conversationState.conversationList = Object.assign(
    [],
    conversationList.value
  );
});
//会话列表name&头像展示处理
const contactsStore = useContactsStore();
//好友属性
const friendUserInfoMap = computed(() => {
    
    
  return contactsStore.friendUserInfoMap;
});
//会话列表头像
const showConversationAvatar = computed(() => {
    
    
  return (item) => {
    
    
    switch (item.chatType) {
    
    
      case CHAT_TYPE.SINGLE_CHAT:
        const friendInfo = friendUserInfoMap.value.get(item.channel_id) || {
    
    };
        return friendInfo.avatarurl ?? conversationState.defaultAvatar;
      case CHAT_TYPE.GROUP_CHAT:
        return conversationState.defaultGroupAvatar;
      default:
        return null;
    }
  };
});
//会话列表名称
const showConversationName = computed(() => {
    
    
  return (item) => {
    
    
    switch (item.chatType) {
    
    
      case CHAT_TYPE.SINGLE_CHAT:
        const friendInfo = friendUserInfoMap.value.get(item.channel_id);
        return friendInfo?.nickname || item.channel_id;
      case CHAT_TYPE.GROUP_CHAT:
        return getGroupName(item.channel_id);
      default:
        return null;
    }
  };
});
//时间展示
const handleTime = computed(() => {
    
    
  return (item) => {
    
    
    return dateFormater('MM/DD/HH:mm', item.time);
  };
});
//删除会话
const deleteConversation = async (eventItem) => {
    
    
  const {
    
     channel_id, chatType } = eventItem;
  try {
    
    
    const res = await uni.showModal({
    
    
      title: '确认删除?',
      confirmText: '删除',
    });
    if (res.confirm) {
    
    
      await removeConversationFromServer(channel_id, chatType);
      conversationStore.deleteConversation(channel_id);
    }
  } catch (error) {
    
    
    uni.showToast({
    
    
      title: '删除失败',
      icon: 'none',
      duration: 2000,
    });
    console.log('删除失败', error);
  }
};

/* 搜索会话相关逻辑 */
//开启搜索模式
const openSearch = () => {
    
    
  conversationState.search_btn = false;
  conversationState.search_chats = true;
  conversationState.gotop = true;
};
//执行搜索方法
const actionSearch = () => {
    
    
  const keyWord = conversationState.search_keyword;
  let resConversationList = [];
  if (keyWord) {
    
    
    resConversationList = conversationStore.conversationList.filter((item) => {
    
    
      if (item.chatType === CHAT_TYPE.SINGLE_CHAT || item.chatType === 'chat') {
    
    
        if (
          friendUserInfoMap.value.has(item.channel_id) &&
          friendUserInfoMap.value.get(item.channel_id)?.nickname
        ) {
    
    
          return (
            item.lastMessage.msg?.indexOf(keyWord) > -1 ||
            item.channel_id?.indexOf(keyWord) > -1 ||
            friendUserInfoMap.value
              .get(item.channel_id)
              .nickname?.indexOf(keyWord) > -1
          );
        } else {
    
    
          return (
            item.lastMessage.msg?.indexOf(keyWord) > -1 ||
            item.channel_id?.indexOf(keyWord) > -1
          );
        }
      }
      if (
        item.chatType === CHAT_TYPE.GROUP_CHAT ||
        item.chatType === 'groupchat'
      ) {
    
    
        return (
          item.channel_id.indexOf(keyWord) > -1 ||
          getGroupName(item.channel_id).indexOf(keyWord) > -1 ||
          item.lastMessage.msg.indexOf(keyWord) > -1
        );
      }
    });
  }
  console.log('>>>>>执行搜索', resConversationList);
  conversationState.conversationList = resConversationList;
};
//取消搜索
const cancelSearch = () => {
    
    
  conversationState.search_btn = true;
  conversationState.search_chats = false;
  conversationState.gotop = false;
  conversationState.conversationList = conversationList.value;
};
//清空搜索框
const clearSearchInput = () => {
    
    
  conversationState.search_keyword = '';
  conversationState.show_clear = false;
};
//输入框事件触发
const onInput = (e) => {
    
    
  let inputValue = e.detail.value;
  if (inputValue) {
    
    
    conversationState.show_clear = true;
  } else {
    
    
    cancelSearch();
  }
};
const close_mask = () => {
    
    
  conversationState.search_btn = true;
  conversationState.search_chats = false;
  conversationState.show_mask = false;
};

/* 获取窗口尺寸 */
const getWindowSize = () => {
    
    
  uni.getSystemInfo({
    
    
    success: (res) => {
    
    
      conversationState.winSize = {
    
    
        witdh: res.windowWidth,
        height: res.windowHeight,
      };
    },
  });
};
const hidePop = () => {
    
    
  conversationState.showPop = false;
};
const entryInform = () => {
    
    
  uni.navigateTo({
    
    
    url: '../notification/notification',
  });
};
const entryemChat = (params) => {
    
    
  console.log('params', params);
  //发送channelack 清除服务端该会话未读数,并且清除本地未读红点
  sendChannelAck(params.channel_id, params.chatType);
  conversationStore.clearConversationUnReadNum(params.channel_id);
  uni.navigateTo({
    
    
    url: `../emChatContainer/index?targetId=${
      
      params.channel_id}&chatType=${
      
      params.chatType}`,
  });
};
onLoad(() => {
    
    
  if (!conversationList.value.length) {
    
    
    fetchConversationList();
  }
});
onShow(() => {
    
    
  uni.hideHomeButton && uni.hideHomeButton();
});
</script>
<style>
@import './conversation.css';
</style>

5. Added Tabbar component code snippet display after refactoring

<template>
  <view :class="isIPX ? 'chatRoom_tab_X' : 'chatRoom_tab'">
    <view class="tableBar" @click="changeTab('conversation')">
      <view
        v-if="unReadSpotNum > 0 || unReadSpotNum == '99+'"
        :class="
          'em-unread-spot ' +
          (unReadSpotNum == '99+' ? 'em-unread-spot-litleFont' : '')
        "
        >{
    
    {
    
     unReadSpotNum + unReadTotalNotNum }}</view
      >
      <image
        :class="unReadSpotNum > 0 || unReadSpotNum == '99+' ? 'haveSpot' : ''"
        :src="
          tabType === 'conversation'
            ? highlightConversationImg
            : conversationImg
        "
      ></image>
      <text :class="tabType === 'conversation' && 'activeText'">会话</text>
    </view>

    <view class="tableBar" @click="changeTab('contacts')">
      <image
        :src="tabType === 'contacts' ? highlightContactsImg : contactsImg"
      ></image>
      <text :class="tabType === 'contacts' && 'activeText'">联系人</text>
    </view>

    <view class="tableBar" @click="changeTab('me')">
      <image :src="tabType === 'me' ? highlightSettingImg : settingImg"></image>
      <text :class="tabType === 'me' && 'activeText'">我的</text>
    </view>
  </view>
</template>

<script setup>
import {
    
     ref, toRefs } from 'vue';
/* images */
const conversationImg = '/static/images/session2x.png';
const highlightConversationImg = '/static/images/sessionhighlight2x.png';
const contactsImg = '/static/images/comtacts2x.png';
const highlightContactsImg = '/static/images/comtactshighlight2x.png';
const settingImg = '/static/images/setting2x.png';
const highlightSettingImg = '/static/images/settinghighlight2x.png';
/* props */
const props = defineProps({
    
    
  tabType: {
    
    
    type: String,
    default: 'conversation',
    required: true,
  },
});
/* emits */
const emits = defineEmits(['switchHomeComponent']);
const {
    
     tabType } = toRefs(props);
const isIPX = ref(false);
const unReadSpotNum = ref(0);
const unReadTotalNotNum = ref(0);

const changeTab = (type) => {
    
    
  emits('switchHomeComponent', type);
};
</script>

<style scoped>
@import './index.css';
</style>

6. After refactoring, add a home component code snippet display

The dynamic component (component) implementation in Vue is not used because uni-app is packaged to some platforms and does not support it.

<template>
  <view>
    <template v-if="isActiveComps === 'conversation'">
      <Conversation />
    </template>
    <template v-if="isActiveComps === 'contacts'">
      <Contacts />
    </template>
    <template v-if="isActiveComps === 'me'">
      <Me />
    </template>
    <Tabbar
      :tab-type="isActiveComps"
      @switchHomeComponent="switchHomeComponent"
    />
  </view>
</template>

<script setup>
import {
    
     ref, watchEffect } from 'vue';
import {
    
     onLoad } from '@dcloudio/uni-app';
/* components */
import Tabbar from '@/layout/tabbar';
import Conversation from '@/pages/conversation/conversation.vue';
import Contacts from '@/pages/contacts/contacts.vue';
import Me from '@/pages/me/me.vue';
const isActiveComps = ref('conversation');

const switchHomeComponent = (type) => {
    
    
  isActiveComps.value = type;
};
/* 设置当前标题 */
const titleMap = {
    
    
  conversation: '会话列表',
  contacts: '联系人',
  me: '我的',
};
watchEffect(() => {
    
    
  uni.setNavigationBarTitle({
    
    
    title: titleMap[isActiveComps.value],
  });
});
onLoad((options) => {
    
    
  //通过路由传参的形式可指定该页面展示某个指定组件
  if (options.page) {
    
    
    switchHomeComponent(options.page);
  }
});
</script>

7. Add emChatContainer component code display after refactoring

<template>
  <div>
    <em-chat />
  </div>
</template>

<script setup>
import {
    
     toRefs, reactive, provide, readonly, computed } from 'vue';
import EmChat from '@/components/emChat';
import {
    
     onNavigationBarButtonTap } from '@dcloudio/uni-app';
import {
    
     useContactsStore } from '@/stores/contacts';
import {
    
     useGroupStore } from '@/stores/group';
import {
    
     CHAT_TYPE } from '@/EaseIM/constant';
const props = defineProps({
    
    
  targetId: {
    
    
    type: String,
    value: '',
    required: true,
  },
  chatType: {
    
    
    type: String,
    value: '',
    required: true,
  },
});

const {
    
     targetId, chatType } = toRefs(reactive(props));
console.log(targetId, chatType);
provide('targetId', readonly(targetId));
provide('chatType', readonly(chatType));

/* 处理NavigationBarTitle展示 */
//群组名称
const groupStore = useGroupStore();
const getGroupName = (groupid) => {
    
    
  const joinedGroupList = groupStore.joinedGroupList;
  let groupName = '';
  if (joinedGroupList.length) {
    
    
    joinedGroupList.forEach((item) => {
    
    
      if (item.groupid === groupid) {
    
    
        console.log(item.groupname);
        return (groupName = item.groupname);
      }
    });
    return groupName;
  } else {
    
    
    return groupid;
  }
};
const contactsStore = useContactsStore();
//好友属性
const friendUserInfoMap = computed(() => {
    
    
  return contactsStore.friendUserInfoMap;
});
//会话列表名称
const getTheIdName = (chatType, targetId) => {
    
    
  switch (chatType) {
    
    
    case CHAT_TYPE.SINGLE_CHAT:
      const friendInfo = friendUserInfoMap.value.get(targetId);
      return friendInfo?.nickname || targetId;
    case CHAT_TYPE.GROUP_CHAT:
      return getGroupName(targetId);
    default:
      return null;
  }
};
uni.setNavigationBarTitle({
    
    
  title: getTheIdName(chatType.value, targetId.value),
});

onNavigationBarButtonTap(() => {
    
    
  uni.navigateTo({
    
    
    url: `/pages/moreMenu/moreMenu?username=${
      
      targetId.value}&type=${
      
      chatType.value}`,
  });
});
</script>

<style scoped>
@import './index.css';
</style>

8. Add some code snippets of EaseIM file after refactoring

config (for IM related configuration files)

export const EM_API_URL = 'https://a1.easemob.com';
export const EM_WEB_SOCKET_URL = 'wss://im-api-wechat.easemob.com/websocket';
export const EM_APP_KEY = 'easemob#easeim';

constant (IM related constants)

export const CHAT_TYPE = {
    
    
  SINGLE_CHAT: 'singleChat',
  GROUP_CHAT: 'groupChat',
};
export const HANDLER_EVENT_NAME = {
    
    
  CONNECT_EVENT: 'connectEvent',
  MESSAGES_EVENT: 'messagesEvent',
  CONTACTS_EVENT: 'contactsEvent',
  GROUP_EVENT: 'groupEvent',
};

export const CONNECT_CALLBACK_TYPE = {
    
    
  CONNECT_CALLBACK: 'connected',
  DISCONNECT_CALLBACK: 'disconnected',
  RECONNECTING_CALLBACK: 'reconnecting',
};

export const MESSAGE_TYPE = {
    
    
  IMAGE: 'img',
  TEXT: 'txt',
  LOCATION: 'location',
  VIDEO: 'video',
  AUDIO: 'audio',
  EMOJI: 'emoji',
  FILE: 'file',
  CUSTOM: 'custom',
};

imApis (SDK interface management)

import {
    
     EMClient } from '../index';
const emContacts = () => {
    
    
  const fetchContactsListFromServer = () => {
    
    
    return new Promise((resolve, reject) => {
    
    
      EMClient.getContacts()
        .then((res) => {
    
    
          const {
    
     data } = res;
          resolve(data);
        })
        .catch((error) => {
    
    
          reject(error);
        });
    });
  };
  const removeContactFromServer = (contactId) => {
    
    
    if (contactId) {
    
    
      EMClient.deleteContact(contactId);
    }
  };
  const addContact = (contactId, applyMsg) => {
    
    
    if (contactId) {
    
    
      EMClient.addContact(contactId, applyMsg);
    }
  };
  const acceptContactInvite = (contactId) => {
    
    
    if (contactId) {
    
    
      EMClient.acceptContactInvite(contactId);
    }
  };
  const declineContactInvite = (contactId) => {
    
    
    if (contactId) {
    
    
      EMClient.declineContactInvite(contactId);
    }
  };
  return {
    
    
    fetchContactsListFromServer,
    removeContactFromServer,
    acceptContactInvite,
    declineContactInvite,
    addContact,
  };
};
export default emContacts;

listener (SDK listener callback management)

import {
    
     EMClient } from '../index';
import {
    
     CONNECT_CALLBACK_TYPE, HANDLER_EVENT_NAME } from '../constant';
export const emConnectListener = (callback, listenerEventName) => {
    
    
  console.log('>>>>连接监听已挂载');
  const connectListenFunc = {
    
    
    onConnected: () => {
    
    
      console.log('connected...');
      callback && callback(CONNECT_CALLBACK_TYPE.CONNECT_CALLBACK);
    },
    onDisconnected: () => {
    
    
      callback && callback(CONNECT_CALLBACK_TYPE.DISCONNECT_CALLBACK);
      console.log('disconnected...');
    },
    onReconnecting: () => {
    
    
      callback && callback(CONNECT_CALLBACK_TYPE.RECONNECTING_CALLBACK);
    },
  };
  EMClient.removeEventHandler(
    listenerEventName || HANDLER_EVENT_NAME.CONNECT_EVENT
  );
  EMClient.addEventHandler(
    listenerEventName || HANDLER_EVENT_NAME.CONNECT_EVENT,
    connectListenFunc
  );
};

utils (IM-related tool function files)

/* 用以获取消息存储格式时的key */
const getEMKey = (loginId, fromId, toId, chatType) => {
    
    
  let key = '';
  if (chatType === 'singleChat') {
    
    
    if (loginId === fromId) {
    
    
      key = toId;
    } else {
    
    
      key = fromId;
    }
  } else if (chatType === 'groupChat') {
    
    
    key = toId;
  }
  return key;
};
export default getEMKey;

index.js (introduce SDK and initialize SDK and export)

import EaseSDK from 'easemob-websdk/uniApp/Easemob-chat';
import {
    
     EM_APP_KEY, EM_API_URL, EM_WEB_SOCKET_URL } from './config';
let EMClient = (uni.EMClient = {
    
    });
EMClient = new EaseSDK.connection({
    
    
  appKey: EM_APP_KEY,
  apiUrl: EM_API_URL,
  url: EM_WEB_SOCKET_URL,
});
uni.EMClient = EMClient;
export {
    
     EaseSDK, EMClient };

9. Display of code snippets for sending messages before and after refactoring

Send text message component before refactoring

<template>
  <!-- <chat-suit-emoji id="chat-suit-emoji" bind:newEmojiStr="emojiAction"></chat-suit-emoji> -->
  <form class="text-input">
    <view :class="mainState.isIPX ? 'f-row-x' : 'f-row'">
      <!-- 发送语音 -->
      <view>
        <image
          class="icon-mic"
          src="/static/images/voice.png"
          @tap="openRecordModal"
        ></image>
      </view>
      <!-- 输入框 -->
      <textarea
        class="f news"
        type="text"
        cursor-spacing="65"
        confirm-type="done"
        v-model.trim="mainState.inputMessage"
        @confirm="sendMessage"
        @input="bindMessage"
        @tap="focus"
        @focus="focus"
        @blur="blur"
        :confirm-hold="mainState.isIPX ? true : false"
        auto-height
        :show-confirm-bar="false"
        maxlength="300"
      />
      <view>
        <image
          class="icon-mic"
          src="/static/images/Emoji.png"
          @tap="openEmoji"
        ></image>
      </view>
      <view v-show="!mainState.inputMessage" @tap="openFunModal">
        <image class="icon-mic" src="/static/images/ad.png"></image>
      </view>
      <button
        class="send-btn-style"
        hover-class="hover"
        @tap="sendMessage"
        v-show="mainState.inputMessage"
      >
        发送
      </button>
    </view>
  </form>
</template>

<script setup>
import {
    
     reactive, toRefs } from 'vue';
import msgType from '@/components/chat/msgtype';
import msgStorage from '@/components/chat/msgstorage';
import disp from '@/utils/broadcast';
const WebIM = uni.WebIM;
/* props */
const props = defineProps({
    
    
  chatParams: {
    
    
    type: Object,
    default: () => ({
    
    }),
  },
  chatType: {
    
    
    type: String,
    default: msgType.chatType.SINGLE_CHAT,
  },
});
const {
    
     chatParams, chatType } = toRefs(props);
/* emits */
const $emits = defineEmits([
  'inputFocused',
  'inputBlured',
  'closeFunModal',
  'closeFunModal',
  'openEmoji',
  'openRecordModal',
  'openFunModal',
]);
const mainState = reactive({
    
    
  inputMessage: '',
  // render input 的值
  userMessage: '', // input 的实时值
  isIPX: false,
});

mainState.isIPX = getApp().globalData.isIPX;
const focus = () => {
    
    
  $emits('inputFocused', null, {
    
    
    bubbles: true,
  });
};
const blur = () => {
    
    
  $emits('inputBlured', null, {
    
    
    bubbles: true,
  });
};
const isGroupChat = () => {
    
    
  return chatType.value == msgType.chatType.CHAT_ROOM;
};

const getSendToParam = () => {
    
    
  console.log('chatParmas', chatParams);
  return isGroupChat() ? chatParams.value.groupId : chatParams.value.your;
};

const bindMessage = (e) => {
    
    
  mainState.userMessage = e.detail.value;
};
const emojiAction = (emoji) => {
    
    
  let str;
  let msglen = mainState.userMessage.length - 1;

  if (emoji && emoji != '[del]') {
    
    
    str = mainState.userMessage + emoji;
  } else if (emoji == '[del]') {
    
    
    let start = mainState.userMessage.lastIndexOf('[');
    let end = mainState.userMessage.lastIndexOf(']');
    let len = end - start;

    if (end != -1 && end == msglen && len >= 3 && len <= 4) {
    
    
      str = mainState.userMessage.slice(0, start);
    } else {
    
    
      str = mainState.userMessage.slice(0, msglen);
    }
  }
  mainState.userMessage = str;
  mainState.inputMessage = str;
};
const sendMessage = () => {
    
    
  if (mainState.userMessage.match(/^\s*$/)) return;
  let id = WebIM.conn.getUniqueId();
  let msg = new WebIM.message(msgType.TEXT, id);
  msg.set({
    
    
    msg: mainState.userMessage,
    from: WebIM.conn.user,
    to: getSendToParam(),
    // roomType: false,
    chatType: isGroupChat()
      ? msgType.chatType.GROUP_CHAT
      : msgType.chatType.SINGLE_CHAT,
    success(id, serverMsgId) {
    
    
      console.log('成功了');
      // 关闭表情弹窗
      $emits.cancelEmoji && $emits.cancelEmoji();
      $emits.closeFunModal && $emits.closeFunModal();
      disp.fire('em.chat.sendSuccess', id, mainState.userMessage);
    },
    fail(id, serverMsgId) {
    
    
      console.log('失败了');
    },
  });

  WebIM.conn.send(msg.body);
  let obj = {
    
    
    msg: msg,
    type: msgType.TEXT,
  };
  saveSendMsg(obj);
  mainState.userMessage = '';
  mainState.inputMessage = '';
  uni.hideKeyboard();
};
const saveSendMsg = (evt) => {
    
    
  msgStorage.saveMsg(evt.msg, evt.type);
};
const openEmoji = () => {
    
    
  $emits('openEmoji');
};
const openRecordModal = () => {
    
    
  $emits('openRecordModal');
};
const openFunModal = () => {
    
    
  $emits('openFunModal');
};
defineExpose({
    
    
  emojiAction,
});
</script>
<style>
@import './main.css';
</style>

Send text message component after refactoring

<template>
  <form class="text-input">
    <view class="f-row">
      <!-- 发送语音 -->
      <view @click="emits('toggleRecordModal')">
        <image class="icon-mic" src="/static/images/voice.png"></image>
      </view>
      <!-- 输入框 -->
      <textarea
        class="f news"
        type="text"
        cursor-spacing="65"
        confirm-type="send"
        v-model.trim="inputContent"
        @focus="inputFocus"
        @confirm="sendTextMessage"
        :confirm-hold="true"
        auto-height
        :show-confirm-bar="false"
        maxlength="300"
      />
      <view @click="emits('openEmojiModal')">
        <image class="icon-mic" src="/static/images/Emoji.png"></image>
      </view>
      <view v-show="!inputContent" @click="emits('openFunModal')">
        <image class="icon-mic" src="/static/images/ad.png"></image>
      </view>
      <button
        class="send-btn-style"
        hover-class="hover"
        @tap="sendTextMessage"
        v-show="inputContent"
      >
        发送
      </button>
    </view>
  </form>
</template>

<script setup>
import {
    
     ref, inject } from 'vue';
import {
    
     emMessages } from '@/EaseIM/imApis';
/* emits */
const emits = defineEmits([
  'toggleRecordModal',
  'openEmojiModal',
  'openFunModal',
  'closeAllModal',
]);
const inputContent = ref('');
//删除输入内容中的emojiMapStr
const delEmojiMapString = () => {
    
    
  if (!inputContent.value) return;
  let newInputContent = '';
  let inputContentlength = inputContent.value.length - 1;

  let start = inputContent.value.lastIndexOf('[');
  let end = inputContent.value.lastIndexOf(']');
  let len = end - start;

  if (end != -1 && end == inputContentlength && len >= 3 && len <= 4) {
    
    
    newInputContent = inputContent.value.slice(0, start);
  } else {
    
    
    newInputContent = inputContent.value.slice(0, inputContentlength);
  }
  inputContent.value = newInputContent;
};
//发送文本消息
const {
    
     sendDisplayMessages } = emMessages();
const injectTargetId = inject('targetId');
const injeactChatType = inject('chatType');
const sendTextMessage = async () => {
    
    
  const params = {
    
    
    // 消息类型。
    type: 'txt',
    // 消息内容。
    msg: inputContent.value,
    // 消息接收方:单聊为对方用户 ID,群聊和聊天室分别为群组 ID 和聊天室 ID。
    to: injectTargetId.value,
    // 会话类型:单聊、群聊和聊天室分别为 `singleChat`、`groupChat` 和 `chatRoom`。
    chatType: injeactChatType.value,
  };
  try {
    
    
    const res = await sendDisplayMessages({
    
     ...params });
    emits('closeAllModal');
    console.log('>>>>>文本消息发送成功', res);
  } catch (error) {
    
    
    console.log('>>>>>文本消息发送失败', error);
    uni.showToast({
    
    
      title: '消息发送失败',
      icon: 'none',
    });
  } finally {
    
    
    inputContent.value = '';
    uni.hideKeyboard();
  }
};
const inputFocus = () => {
    
    
  console.log('>>>>输入框聚焦');
  emits('closeAllModal');
};
defineExpose({
    
    
  inputContent,
  delEmojiMapString,
});
</script>
<style>
@import './index.css';
</style>

10. Code display of messageList before and after refactoring

Message list code before refactoring

<template>
  <view
    scroll-y="true"
    :class="
      msglistState.view + ' wrap ' + (msglistState.isIPX ? 'scroll_view_X' : '')
    "
    @tap="onTap"
    upper-threshold="-50"
    :scroll-into-view="msglistState.toView"
  >
    <view>
      <!-- 弹出举报入口 -->
      <uni-popup ref="alertReport">
        <button @click="showSelectReportType">举报</button>
        <button @click="cannelReport">取消</button>
      </uni-popup>
      <!-- 展示举报选项 -->
      <uni-popup ref="selectReportType">
        <button
          v-for="(item, index) in msglistState.typeList"
          :key="index"
          @click="pickReportType(item)"
        >
          {
    
    {
    
     item.text }}
        </button>
        <button type="warn" @click="hideSelectReportType">取消</button>
      </uni-popup>
      <!-- 填写举报原因 -->
      <uni-popup ref="inputReportReason" type="dialog">
        <uni-popup-dialog
          mode="input"
          title="举报原因"
          placeholder="请输入举报原因"
          @confirm="reportMsg"
          @cancel="msglistState.reason = ''"
        >
          <uni-easyinput
            type="textarea"
            v-model="msglistState.reason"
            placeholder="请填写举报内容"
            :maxlength="300"
          ></uni-easyinput>
        </uni-popup-dialog>
      </uni-popup>
    </view>
    <view class="tips"
      >本应用仅用于环信产品功能开发测试,请勿用于非法用途。任何涉及转账、汇款、裸聊、网恋、网购退款、投资理财等统统都是诈骗,请勿相信!</view
    >
    <view
      @longtap="actionAleartReportMsg(item)"
      class="message"
      v-for="item in msglistState.chatMsg"
      :key="item.mid"
      :id="item.mid"
    >
      <!-- <view class="time">
				<text class="time-text">{
    
    {
    
     item.time }}</text>
      </view>-->
      <view class="main" :class="item.style">
        <view class="user">
          <!-- yourname:就是消息的 from -->
          <text v-if="!item.style" class="user-text">{
    
    {
    
    
            showMessageListNickname(item.yourname) + ' ' + handleTime(item)
          }}</text>
        </view>
        <image class="avatar" :src="showMessageListAvatar(item)" />
        <view class="msg">
          <image
            class="err"
            :class="item.style == 'self' && item.isFail ? 'show' : 'hide'"
            src="/static/images/msgerr.png"
          />

          <image
            v-if="item.style == 'self'"
            src="/static/images/poprightarrow2x.png"
            class="msg_poprightarrow"
          />
          <image
            v-if="item.style == ''"
            src="/static/images/popleftarrow2x.png"
            class="msg_popleftarrow"
          />
          <view
            v-if="
              item.msg.type == msgtype.IMAGE || item.msg.type == msgtype.VIDEO
            "
          >
            <image
              v-if="item.msg.type == msgtype.IMAGE"
              class="avatar"
              :src="item.msg.data"
              style="width: 90px; height: 120px; margin: 2px auto"
              mode="aspectFit"
              @tap="previewImage"
              :data-url="item.msg.data"
            />
            <video
              v-if="item.msg.type == msgtype.VIDEO"
              :src="item.msg.data"
              controls
              style="width: 300rpx"
            />
          </view>
          <audio-msg
            v-if="item.msg.type == msgtype.AUDIO"
            :msg="item"
          ></audio-msg>
          <file-msg v-if="item.msg.type == msgtype.FILE" :msg="item"></file-msg>
          <view
            v-else-if="
              item.msg.type == msgtype.TEXT || item.msg.type == msgtype.EMOJI
            "
          >
            <view
              class="template"
              v-for="(d_item, d_index) in item.msg.data"
              :key="d_index"
            >
              <text
                :data-msg="item"
                v-if="d_item.type == msgtype.TEXT"
                class="msg-text"
                style="float: left"
                selectable="true"
                >{
    
    {
    
     d_item.data }}</text
              >

              <image
                v-if="d_item.type == msgtype.EMOJI"
                class="avatar"
                :src="'/static/images/faces/' + d_item.data"
                style="
                  width: 25px;
                  height: 25px;
                  margin: 0 0 2px 0;
                  float: left;
                "
              />
            </view>
          </view>
          <!-- 个人名片 -->
          <view
            v-else-if="
              item.msg.type == msgtype.CUSTOM && item.customEvent === 'userCard'
            "
            @click="to_profile_page(item.msg.data)"
          >
            <view class="usercard_mian">
              <image
                :src="
                  item.msg.data.avatarurl ||
                  item.msg.data.avatar ||
                  defaultAvatar
                "
              />
              <text class="name">{
    
    {
    
    
                item.msg.data.nickname || item.msg.data.uid
              }}</text>
            </view>
            <!-- <u-divider :use-slot="false" /> -->
            <text>[个人名片]</text>
          </view>
        </view>
      </view>
    </view>
  </view>
  <!-- <view style="height: 1px;"></view> -->
</template>

<script setup>
import {
    
     reactive, ref, computed, onMounted, onUnmounted } from 'vue';
import msgStorage from '../msgstorage';
// let msgStorage = require("../msgstorage");
import disp from '@/utils/broadcast';
import dateFormater from '@/utils/dateFormater';
// let disp = require('../../../utils/broadcast');
import msgtype from '@/components/chat/msgtype';
import audioMsg from './type/audio/audio';
import fileMsg from './type/file';
let LIST_STATUS = {
    
    
  SHORT: 'scroll_view_change',
  NORMAL: 'scroll_view',
};
let page = 0;
let Index = 0;
let curMsgMid = '';
let isFail = false;

const WebIM = uni.WebIM;
/* props */
const props = defineProps({
    
    
  chatParams: {
    
    
    type: Object,
    default: () => ({
    
    }),
    required: true,
  },
});
const {
    
     chatParams } = props;
console.log('msglist', chatParams);
/* emits */
const $emit = defineEmits(['msglistTap']);
const msglistState = reactive({
    
    
  view: LIST_STATUS.NORMAL,
  toView: '',
  chatMsg: [],
  __visibility__: false,
  isIPX: false,
  title: '消息举报',
  list: [
    {
    
    
      text: '举报',
    },
  ],
  rptMsgId: '', // 举报消息id
  rptType: '', // 举报类型
  reason: '',
  typeList: [
    {
    
    
      text: '涉政',
    },
    {
    
    
      text: '涉黄',
    },
    {
    
    
      text: '广告',
    },
    {
    
    
      text: '辱骂',
    },
    {
    
    
      text: '暴恐',
    },
    {
    
    
      text: '违禁',
    },
  ],
  defaultAvatar: '/static/images/theme2x.png',
  defaultGroupAvatar: '/static/images/groupTheme.png',
  usernameObj: null,
});
//做初始参数设置
msglistState.__visibility__ = true;
page = 0;
Index = 0;

onUnmounted(() => {
    
    
  msglistState.__visibility__ = false;
  msgStorage.off('newChatMsg', dispMsg);
});

onMounted(() => {
    
    
  if (getApp().globalData.isIPX) {
    
    
    msglistState.isIPX = true;
  }
  //根据原有uni demo 处理似乎支付宝小程序有参数传递问题,因此针对该平台单独取传递的参数
  if (uni.getSystemInfoSync().uniPlatform === 'mp-alipay') {
    
    
    msglistState.usernameObj = Object.assign({
    
    }, uni.username);
  } else {
    
    
    msglistState.usernameObj = Object.assign({
    
    }, chatParams);
  }
  const usernameObj = msglistState.usernameObj;
  console.log('usernameObj', usernameObj);
  let myUsername = uni.getStorageSync('myUsername');
  let sessionKey = usernameObj.groupId
    ? usernameObj.groupId + myUsername
    : usernameObj.your + myUsername;
  let chatMsg = uni.getStorageSync(sessionKey) || [];
  renderMsg(null, null, chatMsg, sessionKey);
  uni.setStorageSync(sessionKey, null);
  disp.on('em.error.sendMsgErr', function (err) {
    
    
    // curMsgMid = err.data.mid;
    isFail = true;
    // return;
    console.log('发送失败了');
    return;
    let msgList = me.chatMsg;
    msgList.map((item) => {
    
    
      if (
        item.mid.substring(item.mid.length - 10) ==
        curMsgMid.substring(curMsgMid.length - 10)
      ) {
    
    
        item.msg.data[0].isFail = true;
        item.isFail = true;
        me.setData({
    
    
          chatMsg: msgList,
        });
      }
    });
    uni.setStorageSync('rendered_' + sessionKey, msgList);
  });
  msgStorage.on('newChatMsg', dispMsg);
});
/* computed */
//消息列表头像展示
const showMessageListAvatar = computed(() => {
    
    
  const friendUserInfoMap = getApp().globalData.friendUserInfoMap;
  const myUserInfos = getApp().globalData.userInfoFromServer;
  return (item) => {
    
    
    if (!item.style) {
    
    
      if (
        friendUserInfoMap.has(item.username) &&
        friendUserInfoMap.get(item.username)?.avatarurl
      ) {
    
    
        return friendUserInfoMap.get(item.username).avatarurl;
      } else {
    
    
        return msglistState.defaultAvatar;
      }
    } else {
    
    
      if (myUserInfos?.avatarurl) {
    
    
        return myUserInfos.avatarurl;
      } else {
    
    
        return msglistState.defaultAvatar;
      }
    }
  };
});
//消息列表昵称显示
const showMessageListNickname = computed(() => {
    
    
  const friendUserInfoMap = getApp().globalData.friendUserInfoMap;
  return (hxId) => {
    
    
    if (friendUserInfoMap.has(hxId) && friendUserInfoMap.get(hxId)?.nickname) {
    
    
      return friendUserInfoMap.get(hxId).nickname;
    } else {
    
    
      return hxId;
    }
  };
});
//处理时间显示
const handleTime = computed(() => {
    
    
  return (item) => {
    
    
    return dateFormater('MM/DD/HH:mm', item.time);
  };
});

const normalScroll = () => {
    
    
  msglistState.view = LIST_STATUS.NORMAL;
};
//TODO 待优化
//此处用到了发布订阅默认去订阅,msgstorage 文件中 发布的newChatMsg 事件从而取到了存储后的消息list
let curChatMsgList = null;
const dispMsg = (renderableMsg, type, curChatMsg, sesskey) => {
    
    
  const usernameObj = msglistState.usernameObj;
  let myUsername = uni.getStorageSync('myUsername');
  let sessionKey = usernameObj.groupId
    ? usernameObj.groupId + myUsername
    : usernameObj.your + myUsername;
  curChatMsgList = curChatMsg;

  if (!msglistState.__visibility__) return; // 判断是否属于当前会话

  if (usernameObj.groupId) {
    
    
    // 群消息的 to 是 id,from 是 name
    if (
      renderableMsg.info.from == usernameObj.groupId ||
      renderableMsg.info.to == usernameObj.groupId
    ) {
    
    
      if (sesskey == sessionKey) {
    
    
        renderMsg(renderableMsg, type, curChatMsg, sessionKey, 'newMsg');
      }
    }
  } else if (
    renderableMsg.info.from == usernameObj.your ||
    renderableMsg.info.to == usernameObj.your
  ) {
    
    
    if (sesskey == sessionKey) {
    
    
      renderMsg(renderableMsg, type, curChatMsg, sessionKey, 'newMsg');
    }
  }
};
//消息渲染方法
const renderMsg = (renderableMsg, type, curChatMsg, sessionKey, isnew) => {
    
    
  console.log('curChatMsg, sessionKey, isnew', curChatMsg, sessionKey, isnew);
  let historyChatMsgs = uni.getStorageSync('rendered_' + sessionKey) || [];
  historyChatMsgs = historyChatMsgs.concat(curChatMsg);
  if (!historyChatMsgs.length) return;
  if (isnew == 'newMsg') {
    
    
    msglistState.chatMsg = msglistState.chatMsg.concat(curChatMsg);
    msglistState.toView = historyChatMsgs[historyChatMsgs.length - 1].mid;
  } else {
    
    
    msglistState.chatMsg = historyChatMsgs.slice(-10);
    msglistState.toView = historyChatMsgs[historyChatMsgs.length - 1].mid;
  }

  uni.setStorageSync('rendered_' + sessionKey, historyChatMsgs);
  let chatMsg = uni.getStorageSync(sessionKey) || [];
  chatMsg.map(function (item, index) {
    
    
    curChatMsg.map(function (item2, index2) {
    
    
      if (item2.mid == item.mid) {
    
    
        chatMsg.splice(index, 1);
      }
    });
  });
  uni.setStorageSync(sessionKey, chatMsg);
  Index = historyChatMsgs.slice(-10).length;
  // setTimeout 兼容支付宝小程序
  setTimeout(() => {
    
    
    uni.pageScrollTo({
    
    
      scrollTop: 5000,
      duration: 100,
      fail: (e) => {
    
    
        //console.log('滚失败了', e)
      },
    });
  }, 100);

  if (isFail) {
    
    
    renderFail(sessionKey);
  }
};
const renderFail = (sessionKey) => {
    
    
  let msgList = msglistState.chatMsg;
  msgList.map((item) => {
    
    
    if (
      item.mid.substring(item.mid.length - 10) ==
      curMsgMid.substring(curMsgMid.length - 10)
    ) {
    
    
      item.msg.data[0].isFail = true;
      item.isFail = true;
      msglistState.chatMsg = msgList;
    }
  });

  if (curChatMsgList[0].mid == curMsgMid) {
    
    
    curChatMsgList[0].msg.data[0].isShow = false;
    curChatMsgList[0].isShow = false;
  }

  uni.setStorageSync('rendered_' + sessionKey, msgList);
  isFail = false;
};
const onTap = () => {
    
    
  $emit('msglistTap', null, {
    
    
    bubbles: true,
  });
};

const shortScroll = () => {
    
    
  msglistState.view = LIST_STATUS.SHORT;
};

const previewImage = (event) => {
    
    
  var url = event.target.dataset.url;
  uni.previewImage({
    
    
    urls: [url], // 需要预览的图片 http 链接列表
  });
};
const getHistoryMsg = () => {
    
    
  let usernameObj = msglistState.usernameObj;
  let myUsername = uni.getStorageSync('myUsername');
  let sessionKey = usernameObj.groupId
    ? usernameObj.groupId + myUsername
    : usernameObj.your + myUsername;
  let historyChatMsgs = uni.getStorageSync('rendered_' + sessionKey) || [];
  if (Index < historyChatMsgs.length) {
    
    
    let timesMsgList = historyChatMsgs.slice(-Index - 10, -Index);
    msglistState.chatMsg = timesMsgList.concat(msglistState.chatMsg);
    msglistState.toView = timesMsgList[timesMsgList.length - 1].mid;
    Index += timesMsgList.length;
    if (timesMsgList.length == 10) {
    
    
      page++;
    }
    uni.stopPullDownRefresh();
  }
};
const to_profile_page = (userInfo) => {
    
    
  if (userInfo) {
    
    
    uni.navigateTo({
    
    
      url: `../profile/profile?otherProfile=${
      
      JSON.stringify(userInfo)}`,
    });
  }
};

/* 举报消息 */
//弹出举报
const alertReport = ref(null);
const actionAleartReportMsg = (item) => {
    
    
  if (item.style !== 'self') {
    
    
    alertReport.value.open('bottom');
    msglistState.showRpt = true;
    msglistState.rptMsgId = item.mid;
  }
};
//取消举报
const cannelReport = () => {
    
    
  alertReport.value.close();
};

//选择举报类型
const selectReportType = ref(null);
//展示举报类型面板
const showSelectReportType = () => {
    
    
  alertReport.value.close();
  selectReportType.value.open('bottom');
};
const pickReportType = (item) => {
    
    
  msglistState.rptType = item.text;
  hideSelectReportType();
  actionAleartReportReason(item);
};
const hideSelectReportType = () => {
    
    
  selectReportType.value.close();
};
//填写举报原因
const inputReportReason = ref(null);
const actionAleartReportReason = (item) => {
    
    
  console.log('>>>>>>输入举报内容', item);
  inputReportReason.value.open();
};
const reportMsg = () => {
    
    
  if (msglistState.reason === '') {
    
    
    uni.showToast({
    
     title: '请填写举报原因', icon: 'none' });
    return;
  }
  WebIM.conn
    .reportMessage({
    
    
      reportType: msglistState.rptType, // 举报类型
      reportReason: msglistState.reason, // 举报原因。
      messageId: msglistState.rptMsgId, // 上报消息id
    })
    .then(() => {
    
    
      uni.showToast({
    
     title: '举报成功', icon: 'none' });
    })
    .catch((e) => {
    
    
      console.log('>>>>举报失败', e);
      uni.showToast({
    
     title: '举报失败', icon: 'none' });
    })
    .finally(() => {
    
    
      msglistState.reason = '';
      msglistState.rptType = '';
      msglistState.rptMsgId = '';
    });
};
defineExpose({
    
    
  normalScroll,
  getHistoryMsg,
  shortScroll,
});
</script>
<style>
@import './msglist.css';
</style>

Message list code before refactoring

<template>
  <view
    scroll-y="true"
    :class="
      msglistState.view + ' wrap ' + (msglistState.isIPX ? 'scroll_view_X' : '')
    "
    upper-threshold="-50"
    :scroll-into-view="msglistState.toView"
  >
    <view>
      <!-- 弹出举报入口 -->
      <uni-popup ref="alertReport">
        <button @click="showSelectReportType">举报</button>
        <button @click="cannelReport">取消</button>
      </uni-popup>
      <!-- 展示举报选项 -->
      <uni-popup ref="selectReportType">
        <button
          v-for="(item, index) in msglistState.typeList"
          :key="index"
          @click="pickReportType(item)"
        >
          {
    
    {
    
     item.text }}
        </button>
        <button type="warn" @click="hideSelectReportType">取消</button>
      </uni-popup>
      <!-- 填写举报原因 -->
      <uni-popup ref="inputReportReason" type="dialog">
        <uni-popup-dialog
          mode="input"
          title="举报原因"
          placeholder="请输入举报原因"
          @confirm="reportMsg"
          @cancel="msglistState.reason = ''"
        >
          <uni-easyinput
            type="textarea"
            v-model="msglistState.reason"
            placeholder="请填写举报内容"
            :maxlength="300"
          ></uni-easyinput>
        </uni-popup-dialog>
      </uni-popup>
    </view>
    <view class="tips"
      >本应用仅用于环信产品功能开发测试,请勿用于非法用途。任何涉及转账、汇款、裸聊、网恋、网购退款、投资理财等统统都是诈骗,请勿相信!</view
    >
    <view
      @longtap="actionAleartReportMsg(msgBody)"
      class="message"
      v-for="(msgBody, index) in messageList"
      :key="msgBody.id + index + ''"
      :id="msgBody.id"
    >
      <!-- 消息体 -->
      <view class="main" :class="isSelf(msgBody) ? 'self' : ''">
        <view class="user">
          <!-- yourname:就是消息的 from -->
          <text v-if="!isSelf(msgBody)" class="user-text">{
    
    {
    
    
            showMessageListNickname(msgBody.from) + ' ' + handleTime(msgBody)
          }}</text>
        </view>
        <image class="avatar" :src="showMessageListAvatar(msgBody)" />
        <view class="msg">
          <image
            v-if="isSelf(msgBody)"
            src="/static/images/poprightarrow2x.png"
            class="msg_poprightarrow"
          />
          <image
            v-if="!isSelf(msgBody)"
            src="/static/images/popleftarrow2x.png"
            class="msg_popleftarrow"
          />
          <!-- 文本类型消息 -->
          <view v-if="msgBody.type === MESSAGE_TYPE.TEXT">
            <view
              class="template"
              v-for="(d_item, d_index) in parseMsgEmoji(msgBody.msg)"
              :key="d_index"
            >
              <text
                :data-msg="msgBody"
                v-if="d_item.type == MESSAGE_TYPE.TEXT"
                class="msg-text"
                style="float: left"
                selectable="true"
                >{
    
    {
    
     d_item.data }}</text
              >

              <image
                v-if="d_item.type == MESSAGE_TYPE.EMOJI"
                class="avatar"
                :src="'/static/images/faces/' + d_item.data"
                style="
                  width: 25px;
                  height: 25px;
                  margin: 0 0 2px 0;
                  float: left;
                "
              />
            </view>
          </view>
          <!-- 文件类型消息 -->
          <file-msg
            v-if="msgBody.type === MESSAGE_TYPE.FILE"
            :msg="msgBody"
          ></file-msg>
          <!-- 语音片段类型消息 -->
          <audio-msg
            v-if="msgBody.type === MESSAGE_TYPE.AUDIO"
            :msg="msgBody"
          ></audio-msg>
          <!-- 图片以及视频类型消息 -->
          <view
            v-if="
              msgBody.type == MESSAGE_TYPE.IMAGE ||
              msgBody.type == MESSAGE_TYPE.VIDEO
            "
          >
            <image
              v-if="msgBody.type == MESSAGE_TYPE.IMAGE"
              class="avatar"
              :src="msgBody.url"
              style="width: 90px; height: 120px; margin: 2px auto"
              mode="aspectFit"
              @tap="previewImage(msgBody.url)"
            />
            <video
              v-if="msgBody.type == MESSAGE_TYPE.VIDEO"
              :src="msgBody.url"
              controls
              style="width: 300rpx"
            />
          </view>
          <!-- 自定义类型消息 -->
          <view
            v-if="
              msgBody.type == MESSAGE_TYPE.CUSTOM &&
              msgBody.customEvent === 'userCard'
            "
            @click="entryProfilePage(msgBody.customExts)"
          >
            <view class="usercard_mian">
              <image
                :src="
                  msgBody.customExts.avatarurl ||
                  msgBody.customExts.avatar ||
                  msglistState.defaultAvatar
                "
              />
              <text class="name">{
    
    {
    
    
                msgBody.customExts.nickname || msgBody.customExts.uid
              }}</text>
            </view>
            <!-- <u-divider :use-slot="false" /> -->
            <text>[个人名片]</text>
          </view>
        </view>
      </view>
    </view>
  </view>
</template>

<script setup>
import {
    
    
  ref,
  reactive,
  computed,
  watch,
  onMounted,
  inject,
  nextTick,
} from 'vue';
import {
    
     onPullDownRefresh, onNavigationBarButtonTap } from '@dcloudio/uni-app';
/* EaseIM */
import parseEmoji from '@/EaseIM/utils/paseEmoji';
import {
    
     CHAT_TYPE, MESSAGE_TYPE } from '@/EaseIM/constant';
/* stores */
import {
    
     useLoginStore } from '@/stores/login';
import {
    
     useMessageStore } from '@/stores/message';
import {
    
     useContactsStore } from '@/stores/contacts';
/* utils */
import dateFormater from '@/utils/dateFormater';
/* im apis */
import {
    
     emMessages } from '@/EaseIM/imApis';
/* components */
import FileMsg from './type/file';
import AudioMsg from './type/audio/audio';
const msglistState = reactive({
    
    
  isIPX: false,
  toView: 0,
  //漫游当前游标
  view: 'wrap',
  title: '消息举报',
  list: [
    {
    
    
      text: '举报',
    },
  ],
  rptMsgId: '', // 举报消息id
  rptType: '', // 举报类型
  reason: '',
  typeList: [
    {
    
    
      text: '涉政',
    },
    {
    
    
      text: '涉黄',
    },
    {
    
    
      text: '广告',
    },
    {
    
    
      text: '辱骂',
    },
    {
    
    
      text: '暴恐',
    },
    {
    
    
      text: '违禁',
    },
  ],
  defaultAvatar: '/static/images/theme2x.png',
  defaultGroupAvatar: '/static/images/groupTheme.png',
});
const injectTargetId = inject('targetId');
const injectChatType = inject('chatType');
/* 消息相关逻辑处理 */
const {
    
     reportMessages, fetchHistoryMessagesFromServer } = emMessages();
//该用户当前的聊天记录
const messageStore = useMessageStore();
const messageList = computed(() => {
    
    
  return (
    messageStore.messageCollection[injectTargetId.value] ||
    getMoreHistoryMessages() ||
    []
  );
});
//获取更多历史消息
const getMoreHistoryMessages = async () => {
    
    
  const sourceMessage =
    messageStore.messageCollection[injectTargetId.value] || [];
  const cursorMsgId = (sourceMessage.length && sourceMessage[0]?.id) || -1;
  const params = {
    
    
    targetId: injectTargetId.value,
    chatType: injectChatType.value,
    cursor: cursorMsgId,
  };
  try {
    
    
    let res = await fetchHistoryMessagesFromServer(params);
    if (res.messages.length) {
    
    
      messageStore.fetchHistoryPushToMsgCollection(
        injectTargetId.value,
        res.messages.reverse()
      );
    } else {
    
    
      uni.showToast({
    
     title: '暂无更多历史记录', icon: 'none' });
    }
    uni.stopPullDownRefresh();
  } catch (error) {
    
    
    uni.stopPullDownRefresh();
    uni.showToast('历史消息获取失败...');
    console.log('>>>>>返回失败', error);
  }
};
onMounted(() => {
    
    
  nextTick(() => {
    
    
    uni.pageScrollTo({
    
    
      scrollTop: 100000,
      duration: 50,
    });
  });
});
//监听消息内容改变,滚动列表
watch(
  messageList,
  () => {
    
    
    nextTick(() => {
    
    
      uni.pageScrollTo({
    
    
        scrollTop: 100000,
        duration: 100,
      });
    });
  },
  {
    
    
    deep: true,
  }
);
//消息列表头像展示
const loginStore = useLoginStore();
const contactsStore = useContactsStore();
//登录用户属性
const myUserInfos = computed(() => {
    
    
  return loginStore.loginUserProfiles;
});
//好友属性
const friendUserInfoMap = computed(() => {
    
    
  return contactsStore.friendUserInfoMap;
});
//判消息来源是否为自己
const isSelf = computed(() => {
    
    
  return (item) => {
    
    
    return item.from === loginStore.loginUserBaseInfos.loginUserId;
  };
});

const showMessageListAvatar = computed(() => {
    
    
  const friendMap = friendUserInfoMap.value;
  return (item) => {
    
    
    if (item.from !== loginStore.loginUserBaseInfos.loginUserId) {
    
    
      return friendMap.get(item.from)?.avatarurl || msglistState.defaultAvatar;
    } else {
    
    
      return myUserInfos.value?.avatarurl || msglistState.defaultAvatar;
    }
  };
});
//消息列表昵称显示
const showMessageListNickname = computed(() => {
    
    
  const friendMap = friendUserInfoMap.value;
  return (hxId) => {
    
    
    return friendMap.get(hxId)?.nickname || hxId;
  };
});
//处理时间显示
const handleTime = computed(() => {
    
    
  return (item) => {
    
    
    return dateFormater('MM/DD/HH:mm', item.time);
  };
});
//解析表情图片
const parseMsgEmoji = computed(() => {
    
    
  return (content) => {
    
    
    return parseEmoji(content);
  };
});

//预览图片方法
const previewImage = (url) => {
    
    
  uni.previewImage({
    
    
    urls: [url], // 需要预览的图片 http 链接列表
  });
};
//点击查看个人名片
const entryProfilePage = (userInfo) => {
    
    
  if (userInfo) {
    
    
    uni.navigateTo({
    
    
      url: `../profile/profile?otherProfile=${
      
      JSON.stringify(userInfo)}`,
    });
  }
};

/* 举报消息 */
//弹出举报
const alertReport = ref(null);
const actionAleartReportMsg = (item) => {
    
    
  if (item.style !== 'self') {
    
    
    alertReport.value.open('bottom');
    msglistState.showRpt = true;
    msglistState.rptMsgId = item.id;
  }
};
//取消举报
const cannelReport = () => {
    
    
  alertReport.value.close();
};

//选择举报类型
const selectReportType = ref(null);
//展示举报类型面板
const showSelectReportType = () => {
    
    
  alertReport.value.close();
  selectReportType.value.open('bottom');
};
const pickReportType = (item) => {
    
    
  msglistState.rptType = item.text;
  hideSelectReportType();
  actionAleartReportReason(item);
};
const hideSelectReportType = () => {
    
    
  selectReportType.value.close();
};
//填写举报原因
const inputReportReason = ref(null);
const actionAleartReportReason = (item) => {
    
    
  inputReportReason.value.open();
};

const reportMsg = async () => {
    
    
  if (msglistState.reason === '') {
    
    
    uni.showToast({
    
     title: '请填写举报原因', icon: 'none' });
    return;
  }
  const reportParams = {
    
    
    reportType: msglistState.rptType,
    reportReason: msglistState.reason,
    messageId: msglistState.rptMsgId,
  };
  try {
    
    
    await reportMessages({
    
     ...reportParams });
    uni.showToast({
    
     title: '举报成功', icon: 'none' });
  } catch (error) {
    
    
    console.log('>>>>举报失败', error);
    uni.showToast({
    
     title: '举报失败', icon: 'none' });
  } finally {
    
    
    msglistState.reason = '';
    msglistState.rptType = '';
    msglistState.rptMsgId = '';
  }
};
onPullDownRefresh(() => {
    
    
  getMoreHistoryMessages();
  console.log('>>>>>开始了下拉页面');
});
</script>

<style scoped>
@import './index.css';
</style>

There are more refactoring codes that are limited in length and cannot be displayed one by one. If you are interested, please click on the github address at the end of the film to view.

Records of some problems encountered during refactoring

Problem 1. The styles of the three major page components packaged into the WeChat applet are missing.

Brief description of the problem: This problem is displayed normally in H5 and app, but the test found that when running in the WeChat applet, the three page styles of conversation list, contacts, and my page cannot be loaded, and the effect is as follows:

image.png

Troubleshooting solution: It was found that these three components were changed from the original page-level jump to dynamic switching components, but the routing mapping addresses of these three components are still configured in pages.json, resulting in the style being lost and failing to load when it is packaged and run in the WeChat applet. Remove the routing map address that still exists in pages.json to return to normal.
image.png
image.png

Question 2: When packaged into the WeChat applet, it was found that the emoji pictures could not be loaded and displayed normally.

Brief description of the problem: Click emoji to send when packaging to WeChat applet, the sending box cannot display the static resource image mapped by emoji, the effect is as follows:

image.png
Troubleshooting and solution: It was found that there were some problems with the relative path matching resource path in the WeChat applet, and the path was adjusted, as shown in the figure below:

image.png
adjusted

image.png

Question 3. When sending pictures to the WeChat applet, it was found that the type of the intercepted picture was abnormal, resulting in a failure to send.

Description of the problem: When packaged into the WeChat applet, it was found that the function of sending pictures was abnormal, resulting in failure to send messages.

Troubleshooting solution: After troubleshooting, it was found that the WeChat applet started from the basic library 2.21.0, wx.chooseImage stopped maintenance, and uni.chooseMedia needs to be used instead.
Therefore, after processing, it is judged that if it is a WeChat applet platform, byte platform, and JD platform use uni.chooseMedia to select files.
Of course, it should also be noted that the fields returned by uni.chooseMedia and uni.chooseImage are inconsistent, so they need to be processed in a targeted manner when sending them later.

Question 4. When running on the native client (Android, IOS) platform, the function of sending voice, sending pictures, taking pictures and sending pictures will prompt that the XXX module is not loaded.

Description of the problem: When running to the native terminal, when clicking on the attachment message to send, such as sending voice, sending pictures, taking photos and sending pictures, etc., it prompts that the XXX module is not loaded.

Troubleshooting and solution: Before performing cloud packaging in HB, please remember to check the following modules in manifest.json / App module configuration.

image.png

final summary

I am very happy to refactor webim-uniapp-demo. This is something I have always wanted to do, because the original project code can no longer help developers who want to use this project as a reference or reuse it to complete efficient IM function development.

Benefited a lot during the refactoring process. Because in this process, I reorganized and rewritten the code of the entire demo, which not only gave me a deeper understanding of SDK integration, but also made me realize that IM-related functions have more flexibility than traditional business projects. This flexibility may be due to the fact that IM functions usually need to deal with real-time and interactive requirements, and these characteristics also give developers more space to create new functions and experiences. In addition, in this process, I also learned some best practices of the SDK. The use of the combined API in Vue3 also made me feel the flexibility of the Vue3 syntax. The im monitoring, the splitting of the API, and the use of imitation hooks are helpful for subsequent expansion and maintenance.

Experience in any project is valuable, and hopefully it will help me better develop stable, reliable and efficient applications in other projects in the future.

If you have used Huanxin uni-app-demo, if the rewritten code can help you, then this matter is really hot!

Links

Huanxin uni-app document address

Source code address of uni-app-demo-Vue2 version before refactoring

Source code address of uni-app-demo-Vue3 version before refactoring

Source address of uni-app-demo after refactoring

Huanxin uni-app Demo upgrade plan - Vue2 migrated to Vue3 (1)

Finally, one more word, if you find it helpful, please like and support it! There are three plans for this demo (adding audio and video functions), so stay tuned!

Guess you like

Origin blog.csdn.net/huan132456765/article/details/130763984