panhui пре 3 година
родитељ
комит
5d7ccf1aba
4 измењених фајлова са 267 додато и 201 уклоњено
  1. 83 18
      src/components/ChatInfo.vue
  2. 96 167
      src/plugins/chat.js
  3. 2 1
      src/router/index.js
  4. 86 15
      src/views/chat/Detail.vue

+ 83 - 18
src/components/ChatInfo.vue

@@ -1,28 +1,84 @@
 <template>
-    <div class="chat-info" :class="{ isLeft: isLeft }">
-        <div class="chat-time">今天12:10</div>
-        <div class="chat-content">
-            <div class="chat-message">你好,这个藏品怎么支付?</div>
+    <div class="chat-info" :class="{ isLeft: flow == 'in' }">
+        <div class="chat-time" v-if="timeStr">{{ timeStr }}</div>
+        <div class="chat-time" v-if="type == 'notification'">{{ noticeStr }}</div>
+        <div class="chat-content" v-else>
+            <div class="chat-box">
+                <div class="chat-name" v-if="flow == 'in'">{{ info.fromNick }}</div>
+                <div class="chat-message">{{ info.body }}</div>
+            </div>
 
             <van-image width="36" height="36" round src="https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg" />
         </div>
     </div>
 </template>
-<script>
-export default {
-    props: {
-        info: {
-            type: Object,
-            default: () => {
-                return {};
-            }
-        },
-        isLeft: {
-            type: Boolean,
-            default: false
+<script setup>
+import { defineProps, computed } from 'vue';
+import dayjs from 'dayjs';
+
+const props = defineProps({
+    info: {
+        type: Object,
+        default: () => {
+            return {};
+        }
+    },
+    beforeTime: {
+        type: Number,
+        default: 0
+    }
+});
+
+const flow = computed(() => {
+    return props.info.flow;
+});
+// flow: 消息的流向
+// 'in'表示此消息是收到的消息
+// 'out'表示此消息是发出的消息
+
+const type = computed(() => {
+    return props.info.type || 'text';
+});
+
+const noticeStr = computed(() => {
+    if (type.value === 'notification') {
+        if (props.info.attach.type === 'updateTeam') {
+            return props.info.fromNick + '更新了群';
+        }
+        if (props.info.attach.type === 'addTeamMembers') {
+            let users = props.info.attach.users || [];
+            let accounts = props.info.attach.accounts || [];
+            let nicks = users
+                .filter(item => {
+                    return accounts.includes(item.account);
+                })
+                .map(item => {
+                    return item.nick;
+                });
+            return props.info.fromNick + ' 邀请 ' + nicks.join(',') + ' 加入群聊';
+        }
+    }
+    return '';
+});
+
+const timeStr = computed(() => {
+    let day1 = dayjs(props.info.time);
+    let day2 = '';
+    if (props.beforeTime) {
+        day2 = dayjs(props.beforeTime);
+        if (day1.diff(day2, 'minute') < 5) {
+            return '';
         }
     }
-};
+    return day1.calendar(dayjs(), {
+        sameDay: '[今天] HH:mm ', // The same day ( Today at 2:30 AM )
+        nextDay: '[明天] HH:mm', // The next day ( Tomorrow at 2:30 AM )
+        nextWeek: '[下周] DD HH:mm', // The next week ( Sunday at 2:30 AM )
+        lastDay: '[昨天] HH:mm', // The day before ( Yesterday at 2:30 AM )
+        lastWeek: '[上周] DD HH:mm', // Last week ( Last Monday at 2:30 AM )
+        sameElse: 'DD/MM/YYYY HH:mm' // Everything else ( 7/10/2011 )
+    });
+});
 </script>
 
 <style lang="less" scoped>
@@ -39,6 +95,15 @@ export default {
         }
     }
 }
+
+.chat-box {
+    margin: 0 8px;
+    .chat-name {
+        font-size: 12px;
+        color: #939599;
+        line-height: 24px;
+    }
+}
 .chat-time {
     font-size: 12px;
     color: #878d99;
@@ -48,6 +113,7 @@ export default {
 }
 .chat-content {
     .flex();
+    align-items: flex-start;
     justify-content: flex-end;
     .icon {
         width: 36px;
@@ -65,7 +131,6 @@ export default {
         color: #000000;
         line-height: 26px;
         padding: 6px 10px;
-        margin: 0 8px;
     }
 }
 </style>

+ 96 - 167
src/plugins/chat.js

@@ -1,3 +1,4 @@
+// 若使用 IM 能力则引入
 import NIM from 'nim-web-sdk-ng';
 import http from './http';
 import store from '../store';
@@ -59,192 +60,120 @@ var data = {};
 
 var nim = null;
 
+// teamId='7748093008'
+
 function initChat() {
     console.log('34664');
-    http.http.post('/neteaseUser/create?userId=' + store.state.userInfo.id).then(res => {
-        console.log(res);
-        NIM.getInstance({
-            debug: true, // 是否开启日志,将其打印到console。集成开发阶段建议打开。
-            appKey: '872dd9d0a0f8eda25b579654745db459',
-            account: store.state.userInfo.id, //账号
-            token: res.token,
-            db: true, //若不要开启数据库请设置false。SDK默认为true。
-            // privateConf: {}, // 私有化部署方案所需的配置
-            onconnect: onConnect,
-            onroamingmsgs: onRoamingMsgs,
-            onofflinemsgs: onOfflineMsgs,
-            onmsg: onMsg,
-            onwillreconnect: onWillReconnect,
-            ondisconnect: onDisconnect,
-            onerror: onError,
-            onteams: onTeams,
-            onteammembers: onTeamMembers,
-            //onsyncteammembersdone: onSyncTeamMembersDone,
-            onupdateteammember: onUpdateTeamMember
+    http.http
+        .get('/neteaseUser/get/' + store.state.userInfo.id)
+        .then(res => {
+            return Promise.resolve(res);
+        })
+        .catch(() => {
+            return http.http.post('/neteaseUser/create?userId=' + store.state.userInfo.id);
+        })
+        .then(res => {
+            nim = new NIM({
+                debugLevel: 'debug', // 是否开启日志,将其打印到console。集成开发阶段建议打开。
+                appkey: '872dd9d0a0f8eda25b579654745db459',
+                account: store.state.userInfo.id, //账号
+                token: res.token,
+                db: true //若不要开启数据库请设置false。SDK默认为true。
+            });
+
+            nim.connect().then(() => {
+                getTeams();
+                getSession();
+            });
+
+            nim.on('msg', onMsg);
         });
-    });
 }
 
-function onConnect() {
-    console.log('连接成功');
+async function getTeams() {
+    const teams = await nim.team.getTeams();
+    console.log(teams);
 }
-function onWillReconnect(obj) {
-    // 此时说明 SDK 已经断开连接, 请开发者在界面上提示用户连接已断开, 而且正在重新建立连接
-    console.log('即将重连');
-    console.log(obj.retryCount);
-    console.log(obj.duration);
+
+//获取群消息
+async function getTeam(teamId) {
+    const teamInfo = await nim.team.getTeamInfo({
+        teamId: teamId
+    });
+    return teamInfo;
 }
-function onDisconnect(error) {
-    // 此时说明 SDK 处于断开状态, 开发者此时应该根据错误码提示相应的错误信息, 并且跳转到登录页面
-    console.log('丢失连接');
-    console.log(error);
-    if (error) {
-        switch (error.code) {
-            // 账号或者密码错误, 请跳转到登录页面并提示错误
-            case 302:
-                break;
-            // 重复登录, 已经在其它端登录了, 请跳转到登录页面并提示错误
-            case 417:
-                break;
-            // 被踢, 请提示错误后跳转到登录页面
-            case 'kicked':
-                break;
-            default:
-                break;
-        }
-    }
+
+async function getHistroy(teamId) {
+    return await nim.msgLog.getHistoryMsgs({
+        scene: 'team',
+        to: teamId,
+        reverse: false,
+        asc: true
+    });
 }
 
-function onRoamingMsgs(obj) {
-    console.log('收到漫游消息', obj);
-    pushMsg(obj.msgs);
+async function getSession() {
+    const sessions = await nim.session.getSessions({
+        limit: 10,
+        desc: true
+    });
+    console.log(sessions);
 }
-function onOfflineMsgs(obj) {
-    console.log('收到离线消息', obj);
-    pushMsg(obj.msgs);
+
+async function onMsg(msg) {
+    console.log('Receive msg: ', msg);
+    await nim.msg.sendMsgReceipt({
+        msg: msg
+    });
 }
-function onMsg(msg) {
-    console.log('收到消息', msg.scene, msg.type, msg);
-    pushMsg(msg);
-    switch (msg.type) {
-        case 'custom':
-            onCustomMsg(msg);
-            break;
-        case 'notification':
-            // 处理群通知消息
-            onTeamNotificationMsg(msg);
-            break;
-        // 其它case
-        default:
-            break;
+
+async function sendMsg(msg = '', toTeamId, onSendBefore, type = 'Text', scene = 'team') {
+    if (type === 'Text') {
+        await nim.msg.sendTextMsg({
+            scene: scene,
+            to: toTeamId,
+            body: msg,
+            onSendBefore: msg => {
+                onSendBefore(msg);
+            }
+        });
+    } else {
+        await nim.msg.sendImageMsg({
+            scene: scene,
+            to: toTeamId,
+            file: msg,
+            onUploadStart: function (task) {
+                console.log('Upload start!', task);
+            },
+            onUploadProgress: function (progress) {
+                console.log('Uploading!', progress);
+            },
+            onUploadDone: function (file) {
+                console.log('Upload done!', file);
+            },
+            onSendBefore: function (msg) {
+                console.log('Get msg before send', msg);
+            }
+        });
     }
 }
 
-function onTeamNotificationMsg(msg) {}
-
-function pushMsg(msgs) {
+function getPushMsg(msgs, oldMsgs = []) {
     if (!Array.isArray(msgs)) {
         msgs = [msgs];
     }
-    var sessionId = msgs[0].scene + '-' + msgs[0].account;
-    data.msgs = data.msgs || {};
-    data.msgs[sessionId] = nim.mergeMsgs(data.msgs[sessionId], msgs);
-}
-function onCustomMsg(msg) {
-    // 处理自定义消息
-}
-
-function onError(error) {
-    console.log(error);
-}
-
-function onTeams(teams) {
-    console.log('收到群列表', teams);
-    data.teams = nim.mergeTeams(data.teams, teams);
-    onInvalidTeams(teams.invalid);
-}
+    if (!msgs) {
+        return oldMsgs;
+    }
+    if (!msgs.length) {
+        return oldMsgs;
+    }
 
-function onInvalidTeams(teams) {
-    data.teams = nim.cutTeams(data.teams, teams);
-    data.invalidTeams = nim.mergeTeams(data.invalidTeams, teams);
-    refreshTeamsUI();
-}
-// function onCreateTeam(team) {
-//     console.log('你创建了一个群', team);
-//     data.teams = nim.mergeTeams(data.teams, team);
-//     refreshTeamsUI();
-//     onTeamMembers({
-//         teamId: team.teamId,
-//         members: owner
-//     });
-// }
-function refreshTeamsUI() {
-    // 刷新界面
-}
-function onTeamMembers(obj) {
-    console.log('群id', teamId, '群成员', members);
-    var teamId = obj.teamId;
-    var members = obj.members;
-    data.teamMembers = data.teamMembers || {};
-    data.teamMembers[teamId] = nim.mergeTeamMembers(data.teamMembers[teamId], members);
-    data.teamMembers[teamId] = nim.cutTeamMembers(data.teamMembers[teamId], members.invalid);
-    refreshTeamMembersUI();
-}
-// function onSyncTeamMembersDone() {
-//     console.log('同步群成员列表完成');
-// }
-function onUpdateTeamMember(teamMember) {
-    console.log('群成员信息更新了', teamMember);
-    onTeamMembers({
-        teamId: teamMember.teamId,
-        members: teamMember
+    let _back = [...msgs, ...oldMsgs].sort((a, b) => {
+        return a.time - b.time;
     });
-}
-function refreshTeamMembersUI() {
-    // 刷新界面
-}
-
-//发送消息
-function sendMessage(content = '', teamId, type = 'text') {
-    if (content) {
-        if (type == 'text') {
-            nim.sendText({
-                scene: 'team',
-                to: teamId, //消息接收方, 帐号或群id,
-                text: 'hello',
-                done: sendMsgDone
-            });
-        } else {
-            nim.sendFile({
-                scene: 'team',
-                to: teamId, //消息接收方, 帐号或群id,
-                type: 'image', //['text','image']
-                text: 'hello',
-                done: sendMsgDone
-            });
-        }
-    }
-}
 
-function sendMsgDone(error, msg) {
-    console.log(error);
-    console.log(msg);
-    console.log('发送' + msg.scene + ' ' + msg.type + '消息' + (!error ? '成功' : '失败') + ', id=' + msg.idClient);
-    pushMsg(msg);
+    return _back;
 }
 
-export {
-    initChat,
-    onConnect,
-    onWillReconnect,
-    onDisconnect,
-    onError,
-    onTeams,
-    onInvalidTeams,
-    refreshTeamsUI,
-    onTeamMembers,
-    onUpdateTeamMember,
-    refreshTeamMembersUI,
-    onMsg,
-    sendMessage
-};
+export { initChat, getTeam, getHistroy, sendMsg, getPushMsg };

+ 2 - 1
src/router/index.js

@@ -801,7 +801,8 @@ const routes = [
         name: 'chatDetail',
         component: () => import('../views/chat/Detail.vue'),
         meta: {
-            tabColor: '#1C1C1C'
+            tabColor: '#1C1C1C',
+            menuPage: true,
         }
     }
 ];

+ 86 - 15
src/views/chat/Detail.vue

@@ -5,21 +5,40 @@
            
             <van-empty :image="require('@assets/empty_img_asset.png')" v-if="empty" description="没有任何藏品哦~" />
         </van-list> -->
+        <van-sticky>
+            <van-nav-bar :title="title" left-text="" right-text="按钮" left-arrow @click-left="$router.go(-1)" />
+        </van-sticky>
 
-        <div class="van-list" @click="toggleEmoji(false)">
-            <chat-info v-for="i in 3" :key="i" :isLeft="i == 2"></chat-info>
+        <div
+            class="van-list"
+            :style="{
+                height: `calc(var(--app-height) - var(--safe-top) - var(--safe-bottom) - ${showEmoji ? 306 : 106}px)`
+            }"
+            @click="toggleEmoji(false)"
+            ref="listRef"
+        >
+            <chat-info
+                v-for="(item, index) in list"
+                :key="index"
+                :info="item"
+                :beforeTime="index > 0 ? list[index - 1].time : ''"
+            ></chat-info>
         </div>
 
-        <div class="message-bottom">
+        <div class="message-bottom" :style="{ height: showEmoji ? '260px' : '60px' }">
             <div class="message-file">
-                <van-field
-                    type="text"
-                    v-model="message"
-                    :border="false"
-                    clearable
-                    placeholder="请输入需要咨询的问题"
-                    enterkeyhint
-                />
+                <van-form @submit="submit">
+                    <van-field
+                        name="chat"
+                        type="text"
+                        v-model="message"
+                        :border="false"
+                        clearable
+                        placeholder="请输入需要咨询的问题"
+                        enterkeyhint
+                    />
+                </van-form>
+
                 <img class="file-btn" @click="toggleEmoji()" src="@assets/icon_liaotian_biaoqing.png" alt="" />
 
                 <div class="btn-content" :style="{ width: message ? '60px' : '28px' }">
@@ -37,10 +56,12 @@
 </template>
 
 <script setup>
-import { ref } from 'vue';
+import { ref, onMounted, computed } from 'vue';
 import ChatInfo from '../../components/ChatInfo.vue';
 import emojis from '../../emojis.json';
 import { useToggle } from '@vant/use';
+import { getTeam, getHistroy, sendMsg, getPushMsg } from '../../plugins/chat';
+import { nextTick } from 'process';
 
 const emojisPeople = Object.keys(emojis)
     .filter(key => {
@@ -51,31 +72,77 @@ const emojisPeople = Object.keys(emojis)
     });
 
 const message = ref('');
+const teamInfo = ref({});
+const list = ref([]);
 const [showEmoji, toggleEmoji] = useToggle(false);
+const teamId = ref('7748093008');
+const listRef = ref(null);
 
 const chooseEmoji = item => {
     message.value = message.value + item.char;
 };
 
 const send = () => {
-    toggleEmoji(false);
-    console.log(message.value);
+    sendMsg(message.value, teamId.value, msg => {
+        console.log(msg);
+        list.value = getPushMsg(msg, list.value);
+
+        nextTick(() => {
+            goBottom();
+        });
+    });
+    message.value = '';
 };
+
+const title = computed(() => {
+    if (teamInfo.value.name) {
+        return (teamInfo.value.name || '') + '(' + (teamInfo.value.memberNum || '') + '人)';
+    } else {
+        return '';
+    }
+});
+
+function goBottom() {
+    listRef.value.scrollTop = listRef.value.scrollHeight;
+}
+
+function submit(e) {
+    if (e.chat) {
+        send();
+    }
+}
+
+onMounted(() => {
+    setTimeout(() => {
+        getTeam(teamId.value).then(value => {
+            teamInfo.value = value;
+        });
+        getHistroy(teamId.value).then(value => {
+            console.log(value);
+            list.value = value;
+            nextTick(() => {
+                goBottom();
+            });
+        });
+    }, 1000);
+});
 </script>
 <style lang="less" scoped>
 .van-list {
-    min-height: var(--app-height);
+    overflow: auto;
 }
 .page {
     background: #1c1c1c;
 }
 
 .message-bottom {
+    background-color: #222426;
     position: fixed;
     .bottom(0px);
     bottom: 0;
     left: 0;
     right: 0;
+    transition: height ease-in-out 0.3s;
 
     .message-file {
         padding: 9px 16px;
@@ -84,6 +151,9 @@ const send = () => {
             width: 28px;
             height: 28px;
         }
+        .van-form {
+            flex-grow: 1;
+        }
 
         .van-cell {
             --van-cell-vertical-padding: 9px;
@@ -114,6 +184,7 @@ const send = () => {
     padding: 0px 10px 16px;
     height: 200px;
     overflow: auto;
+    box-sizing: border-box;
     .emoji-info {
         width: 10%;
         height: 45px;