前端模拟 ChatGpt 输出效果

163 阅读1分钟

js部分

/**
 * 发送消息
 * @param {string} content 消息框输入的内容
 */
async function sendMessage(content: string): Promise<void> {
    if (!content) {
        return;
    }
    
    // role:1 - user,0 - robot
    chatInfo.value.push({ role: 1, message: content });
    chatInfo.value.push({ role: 0, message: '' });
    // 封装的钩子函数,滚动到底部
    scrollToBottom();
    // 是否禁用输入框
    inputDisabled.value = true;
    // robot对话框消息是否显示加载态
    messageSpinning.value = true;
    inputValue.value = '';
    
    await robotChatApi(content).then(
        (res) => {
            if (res.data.result) {
                const data = res.data.result?.split('');
                let index = 0;
                let timer = null;
                timer = window.requestAnimationFrame(function fn() {
                    if (index < data.length) {
                        messageSpinning.value = false;
                        chatInfo.value[chatInfo.value.length - 1].message += data[index++];
                        scrollToBottom();
                        timer = requestAnimationFrame(fn);
                    } else {
                        cancelAnimationFrame(timer);
                        messageSpinning.value = false;
                    }
                });
             }
        },
        (error) => {
            console.error(error);
            inputDisabled.value = false;
            messageSpinning.value = false;
            if (chatInfo.value[chatInfo.value.length - 1].message === '') {
                chatInfo.value.pop();
            }
        }
    );
}

html部分

<template>
    <!-- 聊天框 -->
    <div class="chat-box">
        <div class="chat-main">
            <div class="chat-content" ref="scrollRef">
                <ul v-for="(item, index) in chatInfo" :key="index">
                    <li :class="item.role === 0 ? 'left' : 'right'">
                        <span v-if="item.role === 0">
                            <img src="@/assets/images/robot.png" class="avatar" />
                        </span>
                        <span class="content">
                            <a-spin
                                size="small"
                                :spinning="messageSpinning && item.role === 0 && index === chatInfo.length - 1"
                                class="load" />
                            {{ item.message }}
                        </span>
                        <span style="vertical-align: top" v-if="item.role === 1">
                            <img src="@/assets/images/user.png" class="avatar" />
                        </span>
                    </li>
                </ul>
            </div>
            <!-- 输入框 -->
            <div class="send-box">
                <a-input
                    :disabled="inputDisabled"
                    validationType="string"
                    v-model:value="inputValue"
                    @keyup.enter="sendMessage(inputValue)">
                    <template v-slot:suffix>
                        <SendOutlined @click="sendMessage(inputValue)" class="svg-icon" />
                    </template>
                </a-input>
            </div>
        </div>
    </div>
</template>

css部分

<style scoped lang="scss">
.chat-box {
    width: 100%;
    display: flex;
    flex-direction: column;
    overflow: scroll;

    .chat-main {
        width: 100%;
        margin: auto;
        .chat-content {
            height: calc(100vh - 196px);
            overflow-x: hidden;
            overflow-y: auto;

            ul {
                list-style: none;
                padding: 10px;
                margin: 0;
                font-size: 14px;
                line-height: 20px;
            }

            li.left {
                margin-right: 20px;
                display: flex;
            }
            .load {
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
            }

            li.left .content {
                display: inline-block;
                border-radius: 5px;
                background-color: #fff;
                padding: 10px 15px;
                margin-left: 8px;
                max-width: 80%;
                position: relative;
            }

            li.right {
                margin-left: 20px;
                flex-wrap: wrap;
                display: flex;
                justify-content: flex-end;
            }

            li.right .content {
                display: inline-block;
                border-radius: 5px;
                background-color: #07c160;
                padding: 10px 15px;
                margin-right: 8px;
                max-width: 80%;
                color: #fff;
            }

            li + li {
                margin-top: 20px;
            }
        }
    }
}
</style>